Initial set of Fabric switch test cases

Change-Id: I86fd2b67d3b773aa496f5ef61f1e1fdf51fd9925
diff --git a/Fabric/Utilities/src/python/oftest/__init__.py b/Fabric/Utilities/src/python/oftest/__init__.py
new file mode 100644
index 0000000..2be5038
--- /dev/null
+++ b/Fabric/Utilities/src/python/oftest/__init__.py
@@ -0,0 +1,56 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
+'''Docstring to silence pylint; ignores --ignore option for __init__.py'''
+import sys
+import os
+import logging
+
+# Global config dictionary
+# Populated by oft.
+config = {}
+
+# Global DataPlane instance used by all tests.
+# Populated by oft.
+dataplane_instance = None
+
+def open_logfile(name):
+    """
+    (Re)open logfile
+
+    When using a log directory a new logfile is created for each test. The same
+    code is used to implement a single logfile in the absence of --log-dir.
+    """
+
+    _format = "%(asctime)s.%(msecs)03d  %(name)-10s: %(levelname)-8s: %(message)s"
+    _datefmt = "%H:%M:%S"
+
+    if config["log_dir"] != None:
+        filename = os.path.join(config["log_dir"], name) + ".log"
+    else:
+        filename = config["log_file"]
+
+    logger = logging.getLogger()
+
+    # Remove any existing handlers
+    for handler in logger.handlers:
+        logger.removeHandler(handler)
+        handler.close()
+
+    # Add a new handler
+    handler = logging.FileHandler(filename, mode='a')
+    handler.setFormatter(logging.Formatter(_format, _datefmt))
+    logger.addHandler(handler)
diff --git a/Fabric/Utilities/src/python/oftest/afpacket.py b/Fabric/Utilities/src/python/oftest/afpacket.py
new file mode 100644
index 0000000..a891d85
--- /dev/null
+++ b/Fabric/Utilities/src/python/oftest/afpacket.py
@@ -0,0 +1,128 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
+"""
+AF_PACKET receive support
+
+When VLAN offload is enabled on the NIC Linux will not deliver the VLAN tag
+in the data returned by recv. Instead, it delivers the VLAN TCI in a control
+message. Python 2.x doesn't have built-in support for recvmsg, so we have to
+use ctypes to call it. The recv function exported by this module reconstructs
+the VLAN tag if it was offloaded.
+"""
+
+import socket
+import struct
+from ctypes import *
+
+ETH_P_8021Q = 0x8100
+SOL_PACKET = 263
+PACKET_AUXDATA = 8
+TP_STATUS_VLAN_VALID = 1 << 4
+
+class struct_iovec(Structure):
+    _fields_ = [
+        ("iov_base", c_void_p),
+        ("iov_len", c_size_t),
+    ]
+
+class struct_msghdr(Structure):
+    _fields_ = [
+        ("msg_name", c_void_p),
+        ("msg_namelen", c_uint32),
+        ("msg_iov", POINTER(struct_iovec)),
+        ("msg_iovlen", c_size_t),
+        ("msg_control", c_void_p),
+        ("msg_controllen", c_size_t),
+        ("msg_flags", c_int),
+    ]
+
+class struct_cmsghdr(Structure):
+    _fields_ = [
+        ("cmsg_len", c_size_t),
+        ("cmsg_level", c_int),
+        ("cmsg_type", c_int),
+    ]
+
+class struct_tpacket_auxdata(Structure):
+    _fields_ = [
+        ("tp_status", c_uint),
+        ("tp_len", c_uint),
+        ("tp_snaplen", c_uint),
+        ("tp_mac", c_ushort),
+        ("tp_net", c_ushort),
+        ("tp_vlan_tci", c_ushort),
+        ("tp_padding", c_ushort),
+    ]
+
+libc = CDLL("libc.so.6")
+recvmsg = libc.recvmsg
+recvmsg.argtypes = [c_int, POINTER(struct_msghdr), c_int]
+recvmsg.retype = c_int
+
+def enable_auxdata(sk):
+    """
+    Ask the kernel to return the VLAN tag in a control message
+
+    Must be called on the socket before afpacket.recv.
+    """
+    sk.setsockopt(SOL_PACKET, PACKET_AUXDATA, 1)
+
+def recv(sk, bufsize):
+    """
+    Receive a packet from an AF_PACKET socket
+    @sk Socket
+    @bufsize Maximum packet size
+    """
+    buf = create_string_buffer(bufsize)
+
+    ctrl_bufsize = sizeof(struct_cmsghdr) + sizeof(struct_tpacket_auxdata) + sizeof(c_size_t)
+    ctrl_buf = create_string_buffer(ctrl_bufsize)
+
+    iov = struct_iovec()
+    iov.iov_base = cast(buf, c_void_p)
+    iov.iov_len = bufsize
+
+    msghdr = struct_msghdr()
+    msghdr.msg_name = None
+    msghdr.msg_namelen = 0
+    msghdr.msg_iov = pointer(iov)
+    msghdr.msg_iovlen = 1
+    msghdr.msg_control = cast(ctrl_buf, c_void_p)
+    msghdr.msg_controllen = ctrl_bufsize
+    msghdr.msg_flags = 0
+
+    rv = recvmsg(sk.fileno(), byref(msghdr), 0)
+    if rv < 0:
+        raise RuntimeError("recvmsg failed: rv=%d", rv)
+
+    # The kernel only delivers control messages we ask for. We
+    # only enabled PACKET_AUXDATA, so we can assume it's the
+    # only control message.
+    assert msghdr.msg_controllen >= sizeof(struct_cmsghdr)
+
+    cmsghdr = struct_cmsghdr.from_buffer(ctrl_buf) # pylint: disable=E1101
+    assert cmsghdr.cmsg_level == SOL_PACKET
+    assert cmsghdr.cmsg_type == PACKET_AUXDATA
+
+    auxdata = struct_tpacket_auxdata.from_buffer(ctrl_buf, sizeof(struct_cmsghdr)) # pylint: disable=E1101
+
+    if auxdata.tp_vlan_tci != 0 or auxdata.tp_status & TP_STATUS_VLAN_VALID:
+        # Insert VLAN tag
+        tag = struct.pack("!HH", ETH_P_8021Q, auxdata.tp_vlan_tci)
+        return buf.raw[:12] + tag + buf.raw[12:rv]
+    else:
+        return buf.raw[:rv]
diff --git a/Fabric/Utilities/src/python/oftest/base_tests.py b/Fabric/Utilities/src/python/oftest/base_tests.py
new file mode 100755
index 0000000..7eb68bc
--- /dev/null
+++ b/Fabric/Utilities/src/python/oftest/base_tests.py
@@ -0,0 +1,162 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
+"""
+Base classes for test cases
+
+Tests will usually inherit from one of these classes to have the controller
+and/or dataplane automatically set up.
+"""
+
+import logging
+import unittest
+import os
+
+import oftest
+from oftest import config
+import oftest.controller as controller
+import oftest.dataplane as dataplane
+import ofp
+from ofdpa_utils import *
+
+class BaseTest(unittest.TestCase):
+    def __str__(self):
+        return self.id().replace('.runTest', '')
+
+    def setUp(self):
+        if config["force_ofdpa_restart"]:
+            logging.info("Restarting OFDPA")
+            forceOfdpaRestart( config["force_ofdpa_restart"]);
+        oftest.open_logfile(str(self))
+        logging.info("** START TEST CASE " + str(self))
+
+    def tearDown(self):
+        logging.info("** END TEST CASE " + str(self))
+        if config["force_ofdpa_restart"]:
+            forceOfdpaStop( config["force_ofdpa_restart"]);
+
+
+class SimpleProtocol(BaseTest):
+    """
+    Root class for setting up the controller
+    """
+
+    def setUp(self):
+        BaseTest.setUp(self)
+        self.controller = controller.Controller(
+            switch=config["switch_ip"],
+            host=config["controller_host"],
+            port=config["controller_port"])
+        self.controller.start()
+
+        try:
+            #@todo Add an option to wait for a pkt transaction to ensure version
+            # compatibilty?
+            self.controller.connect(timeout=20)
+
+            # By default, respond to echo requests
+            self.controller.keep_alive = True
+            if not self.controller.active:
+                raise Exception("Controller startup failed")
+            if self.controller.switch_addr is None:
+                raise Exception("Controller startup failed (no switch addr)")
+            logging.info("Connected " + str(self.controller.switch_addr))
+            request = ofp.message.features_request()
+            reply, pkt = self.controller.transact(request)
+            self.assertTrue(reply is not None,
+                            "Did not complete features_request for handshake")
+            if reply.version == 1:
+                self.supported_actions = reply.actions
+                logging.info("Supported actions: " + hex(self.supported_actions))
+        except:
+            self.controller.kill()
+            del self.controller
+            raise
+
+    def inheritSetup(self, parent):
+        """
+        Inherit the setup of a parent
+
+        This allows running at test from within another test.  Do the
+        following:
+
+        sub_test = SomeTestClass()  # Create an instance of the test class
+        sub_test.inheritSetup(self) # Inherit setup of parent
+        sub_test.runTest()          # Run the test
+
+        Normally, only the parent's setUp and tearDown are called and
+        the state after the sub_test is run must be taken into account
+        by subsequent operations.
+        """
+        logging.info("** Setup " + str(self) + " inheriting from "
+                          + str(parent))
+        self.controller = parent.controller
+        self.supported_actions = parent.supported_actions
+        
+    def tearDown(self):
+        self.controller.shutdown()
+        self.controller.join()
+        del self.controller
+        BaseTest.tearDown(self)
+
+    def assertTrue(self, cond, msg):
+        if not cond:
+            logging.error("** FAILED ASSERTION: " + msg)
+        unittest.TestCase.assertTrue(self, cond, msg)
+
+class SimpleDataPlane(SimpleProtocol):
+    """
+    Root class that sets up the controller and dataplane
+    """
+    def setUp(self):
+        SimpleProtocol.setUp(self)
+        self.dataplane = oftest.dataplane_instance
+        self.dataplane.flush()
+        if config["log_dir"] != None:
+            filename = os.path.join(config["log_dir"], str(self)) + ".pcap"
+            self.dataplane.start_pcap(filename)
+
+    def inheritSetup(self, parent):
+        """
+        Inherit the setup of a parent
+
+        See SimpleProtocol.inheritSetup
+        """
+        SimpleProtocol.inheritSetup(self, parent)
+        self.dataplane = parent.dataplane
+
+    def tearDown(self):
+        if config["log_dir"] != None:
+            self.dataplane.stop_pcap()
+        SimpleProtocol.tearDown(self)
+
+class DataPlaneOnly(BaseTest):
+    """
+    Root class that sets up only the dataplane
+    """
+
+    def setUp(self):
+        BaseTest.setUp(self)
+        self.dataplane = oftest.dataplane_instance
+        self.dataplane.flush()
+        if config["log_dir"] != None:
+            filename = os.path.join(config["log_dir"], str(self)) + ".pcap"
+            self.dataplane.start_pcap(filename)
+
+    def tearDown(self):
+        if config["log_dir"] != None:
+            self.dataplane.stop_pcap()
+        BaseTest.tearDown(self)
diff --git a/Fabric/Utilities/src/python/oftest/controller.py b/Fabric/Utilities/src/python/oftest/controller.py
new file mode 100755
index 0000000..e52bfbe
--- /dev/null
+++ b/Fabric/Utilities/src/python/oftest/controller.py
@@ -0,0 +1,767 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
+"""
+OpenFlow Test Framework
+
+Controller class
+
+Provide the interface to the control channel to the switch under test.  
+
+Class inherits from thread so as to run in background allowing
+asynchronous callbacks (if needed, not required).  Also supports
+polling.
+
+The controller thread maintains a queue.  Incoming messages that
+are not handled by a callback function are placed in this queue for 
+poll calls.  
+
+Callbacks and polling support specifying the message type
+
+@todo Support transaction semantics via xid
+@todo Support select and listen on an administrative socket (or
+use a timeout to support clean shutdown).
+
+Currently only one connection is accepted during the life of
+the controller.   There seems
+to be no clean way to interrupt an accept call.  Using select that also listens
+on an administrative socket and can shut down the socket might work.
+
+"""
+
+import sys
+import os
+import socket
+import time
+import struct
+import select
+import logging
+from threading import Thread
+from threading import Lock
+from threading import Condition
+
+import ofutils
+import loxi
+
+# Configured openflow version
+import ofp as cfg_ofp
+
+FILTER = ''.join( [ (len( repr( chr( x ) ) ) == 3) and chr( x ) or '.'
+                    for x in range( 256 ) ] )
+
+
+def hex_dump_buffer( src, length=16 ):
+    """
+    Convert src to a hex dump string and return the string
+    @param src The source buffer
+    @param length The number of bytes shown in each line
+    @returns A string showing the hex dump
+    """
+    result = [ "\n" ]
+    for i in xrange( 0, len( src ), length ):
+        chars = src[ i:i + length ]
+        hex = ' '.join( [ "%02x" % ord( x ) for x in chars ] )
+        printable = ''.join( [ "%s" % ((ord( x ) <= 127 and
+                                        FILTER[ ord( x ) ]) or '.') for x in
+                               chars ] )
+        result.append( "%04x  %-*s  %s\n" % (i, length * 3, hex, printable) )
+    return ''.join( result )
+
+
+##@todo Find a better home for these identifiers (controller)
+RCV_SIZE_DEFAULT = 32768
+LISTEN_QUEUE_SIZE = 1
+
+
+class Controller( Thread ):
+    """
+    Class abstracting the control interface to the switch.  
+
+    For receiving messages, two mechanism will be implemented.  First,
+    query the interface with poll.  Second, register to have a
+    function called by message type.  The callback is passed the
+    message type as well as the raw packet (or message object)
+
+    One of the main purposes of this object is to translate between network 
+    and host byte order.  'Above' this object, things should be in host
+    byte order.
+
+    @todo Consider using SocketServer for listening socket
+    @todo Test transaction code
+
+    @var rcv_size The receive size to use for receive calls
+    @var max_pkts The max size of the receive queue
+    @var keep_alive If true, listen for echo requests and respond w/
+    echo replies
+    @var initial_hello If true, will send a hello message immediately
+    upon connecting to the switch
+    @var switch If not None, do an active connection to the switch
+    @var host The host to use for connect
+    @var port The port to connect on 
+    @var packets_total Total number of packets received
+    @var packets_expired Number of packets popped from queue as queue full
+    @var packets_handled Number of packets handled by something
+    @var dbg_state Debug indication of state
+    """
+
+    def __init__( self, switch=None, host='127.0.0.1', port=6653, max_pkts=1024,
+                  force=False ):
+        Thread.__init__( self )
+        # Socket related
+        self.rcv_size = RCV_SIZE_DEFAULT
+        self.listen_socket = None
+        self.switch_socket = None
+        self.switch_addr = None
+        self.connect_cv = Condition( )
+        self.message_cv = Condition( )
+        self.tx_lock = Lock( )
+
+        # Used to wake up the event loop from another thread
+        self.waker = ofutils.EventDescriptor( )
+
+        # Counters
+        self.socket_errors = 0
+        self.parse_errors = 0
+        self.packets_total = 0
+        self.packets_expired = 0
+        self.packets_handled = 0
+        self.poll_discards = 0
+
+        # State
+        self.sync = Lock( )
+        self.handlers = { }
+        self.keep_alive = False
+        self.active = True
+        self.initial_hello = True
+
+        # OpenFlow message/packet queue
+        # Protected by the packets_cv lock / condition variable
+        self.packets = [ ]
+        self.packets_cv = Condition( )
+        self.packet_in_count = 0
+
+        # Settings
+        self.max_pkts = max_pkts
+        self.switch = switch
+        self.passive = not self.switch
+        self.force = force
+        self.host = host
+        self.port = port
+        self.dbg_state = "init"
+        self.logger = logging.getLogger( "controller" )
+        self.filter_packet_in = False  # Drop "excessive" packet ins
+        self.pkt_in_run = 0  # Count on run of packet ins
+        self.pkt_in_filter_limit = 50  # Count on run of packet ins
+        self.pkt_in_dropped = 0  # Total dropped packet ins
+        self.transact_to = 15  # Transact timeout default value; add to config
+
+        # Transaction and message type waiting variables 
+        #   xid_cv: Condition variable (semaphore) for packet waiters
+        #   xid: Transaction ID being waited on
+        #   xid_response: Transaction response message
+        self.xid_cv = Condition( )
+        self.xid = None
+        self.xid_response = None
+
+        self.buffered_input = ""
+
+        # Create listen socket
+        if self.passive:
+            self.logger.info( "Create/listen at " + self.host + ":" +
+                              str( self.port ) )
+            ai = socket.getaddrinfo( self.host, self.port, socket.AF_UNSPEC,
+                                     socket.SOCK_STREAM, 0, socket.AI_PASSIVE )
+            # Use first returned addrinfo
+            (family, socktype, proto, name, sockaddr) = ai[ 0 ]
+            self.listen_socket = socket.socket( family, socktype )
+            self.listen_socket.setsockopt( socket.SOL_SOCKET,
+                                           socket.SO_REUSEADDR, 1 )
+            self.listen_socket.bind( sockaddr )
+            self.listen_socket.listen( LISTEN_QUEUE_SIZE )
+
+    def filter_packet( self, rawmsg, hdr ):
+        """
+        Check if packet should be filtered
+
+        Currently filters packet in messages
+        @return Boolean, True if packet should be dropped
+        """
+        # XXX didn't actually check for packet-in...
+        return False
+        # Add check for packet in and rate limit
+        if self.filter_packet_in:
+            # If we were dropping packets, report number dropped
+            # TODO dont drop expected packet ins
+            if self.pkt_in_run > self.pkt_in_filter_limit:
+                self.logger.debug( "Dropped %d packet ins (%d total)"
+                                   % ((self.pkt_in_run -
+                                       self.pkt_in_filter_limit),
+                                      self.pkt_in_dropped) )
+            self.pkt_in_run = 0
+
+        return False
+
+    def _pkt_handle( self, pkt ):
+        """
+        Check for all packet handling conditions
+
+        Parse and verify message 
+        Check if XID matches something waiting
+        Check if message is being expected for a poll operation
+        Check if keep alive is on and message is an echo request
+        Check if any registered handler wants the packet
+        Enqueue if none of those conditions is met
+
+        an echo request in case keep_alive is true, followed by
+        registered message handlers.
+        @param pkt The raw packet (string) which may contain multiple OF msgs
+        """
+
+        # snag any left over data from last read()
+        pkt = self.buffered_input + pkt
+        self.buffered_input = ""
+
+        # Process each of the OF msgs inside the pkt
+        offset = 0
+        while offset < len( pkt ):
+            if offset + 8 > len( pkt ):
+                break
+
+            # Parse the header to get type
+            hdr_version, hdr_type, hdr_length, hdr_xid = cfg_ofp.message.parse_header(
+                    pkt[ offset: ] )
+
+            # Use loxi to resolve to ofp of matching version
+            ofp = loxi.protocol( hdr_version )
+
+            # Extract the raw message bytes
+            if (offset + hdr_length) > len( pkt ):
+                break
+            rawmsg = pkt[ offset: offset + hdr_length ]
+            offset += hdr_length
+
+            # if self.filter_packet(rawmsg, hdr):
+            #    continue
+
+            msg = ofp.message.parse_message( rawmsg )
+            if not msg:
+                self.parse_errors += 1
+                self.logger.warn( "Could not parse message" )
+                continue
+
+            self.logger.debug( "Msg in: version %d class %s len %d xid %d",
+                               hdr_version, type( msg ).__name__, hdr_length,
+                               hdr_xid )
+
+            with self.sync:
+                # Check if transaction is waiting
+                with self.xid_cv:
+                    if self.xid and hdr_xid == self.xid:
+                        self.logger.debug(
+                                "Matched expected XID " + str( hdr_xid ) )
+                        self.xid_response = (msg, rawmsg)
+                        self.xid = None
+                        self.xid_cv.notify( )
+                        continue
+
+                # Check if keep alive is set; if so, respond to echo requests
+                if self.keep_alive:
+                    if hdr_type == ofp.OFPT_ECHO_REQUEST:
+                        self.logger.debug( "Responding to echo request" )
+                        rep = ofp.message.echo_reply( )
+                        rep.xid = hdr_xid
+                        # Ignoring additional data
+                        self.message_send( rep )
+                        continue
+
+                # Generalize to counters for all packet types?
+                if msg.type == ofp.OFPT_PACKET_IN:
+                    self.packet_in_count += 1
+
+                # Log error messages
+                if isinstance( msg, ofp.message.error_msg ):
+                    # pylint: disable=E1103
+                    if msg.err_type in ofp.ofp_error_type_map:
+                        type_str = ofp.ofp_error_type_map[ msg.err_type ]
+                        if msg.err_type == ofp.OFPET_HELLO_FAILED:
+                            code_map = ofp.ofp_hello_failed_code_map
+                        elif msg.err_type == ofp.OFPET_BAD_REQUEST:
+                            code_map = ofp.ofp_bad_request_code_map
+                        elif msg.err_type == ofp.OFPET_BAD_ACTION:
+                            code_map = ofp.ofp_bad_action_code_map
+                        elif msg.err_type == ofp.OFPET_FLOW_MOD_FAILED:
+                            code_map = ofp.ofp_flow_mod_failed_code_map
+                        elif msg.err_type == ofp.OFPET_PORT_MOD_FAILED:
+                            code_map = ofp.ofp_port_mod_failed_code_map
+                        elif msg.err_type == ofp.OFPET_QUEUE_OP_FAILED:
+                            code_map = ofp.ofp_queue_op_failed_code_map
+                        else:
+                            code_map = None
+
+                        if code_map and msg.code in code_map:
+                            code_str = code_map[ msg.code ]
+                        else:
+                            code_str = "unknown"
+                    else:
+                        type_str = "unknown"
+                        code_str = "unknown"
+                    self.logger.warn(
+                            "Received error message: xid=%d type=%s (%d) code=%s (%d)",
+                            hdr_xid, type_str, msg.err_type, code_str,
+                            msg.code )
+
+                # Now check for message handlers; preference is given to
+                # handlers for a specific packet
+                handled = False
+                if hdr_type in self.handlers.keys( ):
+                    handled = self.handlers[ hdr_type ]( self, msg, rawmsg )
+                if not handled and ("all" in self.handlers.keys( )):
+                    handled = self.handlers[ "all" ]( self, msg, rawmsg )
+
+                if not handled:  # Not handled, enqueue
+                    with self.packets_cv:
+                        if len( self.packets ) >= self.max_pkts:
+                            self.packets.pop( 0 )
+                            self.packets_expired += 1
+                        self.packets.append( (msg, rawmsg) )
+                        self.packets_cv.notify_all( )
+                    self.packets_total += 1
+                else:
+                    self.packets_handled += 1
+                    self.logger.debug( "Message handled by callback" )
+
+        # end of 'while offset < len(pkt)'
+        #   note that if offset = len(pkt), this is
+        #   appends a harmless empty string
+        self.buffered_input += pkt[ offset: ]
+
+    def _socket_ready_handle( self, s ):
+        """
+        Handle an input-ready socket
+
+        @param s The socket object that is ready
+        @returns 0 on success, -1 on error
+        """
+
+        if self.passive and s and s == self.listen_socket:
+            if self.switch_socket:
+                self.logger.warning(
+                        "Ignoring incoming connection; already connected to switch" )
+                (sock, addr) = self.listen_socket.accept( )
+                sock.close( )
+                return 0
+
+            try:
+                (sock, addr) = self.listen_socket.accept( )
+            except:
+                self.logger.warning( "Error on listen socket accept" )
+                return -1
+            self.logger.info( self.host + ":" + str(
+                    self.port ) + ": Incoming connection from " + str( addr ) )
+
+            with self.connect_cv:
+                (self.switch_socket, self.switch_addr) = (sock, addr)
+                self.switch_socket.setsockopt( socket.IPPROTO_TCP,
+                                               socket.TCP_NODELAY, True )
+                if self.initial_hello:
+                    self.message_send( cfg_ofp.message.hello( ) )
+                self.connect_cv.notify( )  # Notify anyone waiting
+
+            # Prevent further connections
+            self.listen_socket.close( )
+            self.listen_socket = None
+        elif s and s == self.switch_socket:
+            for idx in range( 3 ):  # debug: try a couple of times
+                try:
+                    pkt = self.switch_socket.recv( self.rcv_size )
+                except:
+                    self.logger.warning( "Error on switch read" )
+                    return -1
+
+                if not self.active:
+                    return 0
+
+                if len( pkt ) == 0:
+                    self.logger.warning( "Zero-length switch read, %d" % idx )
+                else:
+                    break
+
+            if len( pkt ) == 0:  # Still no packet
+                self.logger.warning( "Zero-length switch read; closing cxn" )
+                self.logger.info( str( self ) )
+                return -1
+
+            self._pkt_handle( pkt )
+        elif s and s == self.waker:
+            self.waker.wait( )
+        else:
+            self.logger.error( "Unknown socket ready: " + str( s ) )
+            return -1
+
+        return 0
+
+    def active_connect( self ):
+        """
+        Actively connect to a switch IP addr
+        """
+        try:
+            self.logger.info( "Trying active connection to %s" % self.switch )
+            soc = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
+            soc.connect( (self.switch, self.port) )
+            self.logger.info( "Connected to " + self.switch + " on " +
+                              str( self.port ) )
+            soc.setsockopt( socket.IPPROTO_TCP, socket.TCP_NODELAY, True )
+            self.switch_addr = (self.switch, self.port)
+            return soc
+        except (StandardError, socket.error), e:
+            self.logger.error( "Could not connect to %s at %d:: %s" %
+                               (self.switch, self.port, str( e )) )
+        return None
+
+    def wakeup( self ):
+        """
+        Wake up the event loop, presumably from another thread.
+        """
+        self.waker.notify( )
+
+    def sockets( self ):
+        """
+        Return list of sockets to select on.
+        """
+        socs = [ self.listen_socket, self.switch_socket, self.waker ]
+        return [ x for x in socs if x ]
+
+    def run( self ):
+        """
+        Activity function for class
+
+        Assumes connection to switch already exists.  Listens on
+        switch_socket for messages until an error (or zero len pkt)
+        occurs.
+
+        When there is a message on the socket, check for handlers; queue the
+        packet if no one handles the packet.
+
+        See note for controller describing the limitation of a single
+        connection for now.
+        """
+
+        self.dbg_state = "running"
+
+        while self.active:
+            try:
+                sel_in, sel_out, sel_err = \
+                    select.select( self.sockets( ), [ ], self.sockets( ), 1 )
+            except:
+                print sys.exc_info( )
+                self.logger.error( "Select error, disconnecting" )
+                self.disconnect( )
+
+            for s in sel_err:
+                self.logger.error(
+                        "Got socket error on: " + str( s ) + ", disconnecting" )
+                self.disconnect( )
+
+            for s in sel_in:
+                if self._socket_ready_handle( s ) == -1:
+                    self.disconnect( )
+
+        # End of main loop
+        self.dbg_state = "closing"
+        self.logger.info( "Exiting controller thread" )
+        self.shutdown( )
+
+    def connect( self, timeout=-1 ):
+        """
+        Connect to the switch
+
+        @param timeout Block for up to timeout seconds. Pass -1 for the default.
+        @return Boolean, True if connected
+        """
+
+        if not self.passive:  # Do active connection now
+            self.logger.info( "Attempting to connect to %s on port %s" %
+                                  (self.switch, str( self.port )) )
+            soc = self.active_connect( )
+            if soc:
+                self.logger.info( "Connected to %s", self.switch )
+                self.dbg_state = "running"
+                self.switch_socket = soc
+                self.wakeup( )
+                with self.connect_cv:
+                    if self.initial_hello:
+                        self.message_send( cfg_ofp.message.hello( ) )
+                    self.connect_cv.notify( )  # Notify anyone waiting
+            else:
+                self.logger.error( "Could not actively connect to switch %s",
+                                   self.switch )
+                self.active = False
+        else:
+            with self.connect_cv:
+                ofutils.timed_wait( self.connect_cv, lambda: self.switch_socket,
+                                    timeout=timeout )
+
+        return self.switch_socket is not None
+
+    def disconnect( self, timeout=-1 ):
+        """
+        If connected to a switch, disconnect.
+        """
+        if self.switch_socket:
+            self.switch_socket.close( )
+            self.switch_socket = None
+            self.switch_addr = None
+            with self.packets_cv:
+                self.packets = [ ]
+            with self.connect_cv:
+                self.connect_cv.notifyAll( )
+
+    def wait_disconnected( self, timeout=-1 ):
+        """
+        @param timeout Block for up to timeout seconds. Pass -1 for the default.
+        @return Boolean, True if disconnected
+        """
+
+        with self.connect_cv:
+            ofutils.timed_wait( self.connect_cv,
+                                lambda: True if not self.switch_socket else None,
+                                timeout=timeout )
+        return self.switch_socket is None
+
+    def kill( self ):
+        """
+        Force the controller thread to quit
+        """
+        self.active = False
+        self.wakeup( )
+        self.join( )
+
+    def shutdown( self ):
+        """
+        Shutdown the controller closing all sockets
+
+        @todo Might want to synchronize shutdown with self.sync...
+        """
+
+        self.active = False
+        try:
+            self.switch_socket.shutdown( socket.SHUT_RDWR )
+        except:
+            self.logger.info( "Ignoring switch soc shutdown error" )
+        self.switch_socket = None
+
+        try:
+            self.listen_socket.shutdown( socket.SHUT_RDWR )
+        except:
+            self.logger.info( "Ignoring listen soc shutdown error" )
+        self.listen_socket = None
+
+        # Wakeup condition variables on which controller may be wait
+        with self.xid_cv:
+            self.xid_cv.notifyAll( )
+
+        with self.connect_cv:
+            self.connect_cv.notifyAll( )
+
+        self.wakeup( )
+        self.dbg_state = "down"
+
+    def register( self, msg_type, handler ):
+        """
+        Register a callback to receive a specific message type.
+
+        Only one handler may be registered for a given message type.
+
+        WARNING:  A lock is held during the handler call back, so 
+        the handler should not make any blocking calls
+
+        @param msg_type The type of message to receive.  May be DEFAULT 
+        for all non-handled packets.  The special type, the string "all"
+        will send all packets to the handler.
+        @param handler The function to call when a message of the given 
+        type is received.
+        """
+        # Should check type is valid
+        if not handler and msg_type in self.handlers.keys( ):
+            del self.handlers[ msg_type ]
+            return
+        self.handlers[ msg_type ] = handler
+
+    def poll( self, exp_msg=None, timeout=-1 ):
+        """
+        Wait for the next OF message received from the switch.
+
+        @param exp_msg If set, return only when this type of message 
+        is received (unless timeout occurs).
+
+        @param timeout Maximum number of seconds to wait for the message.
+        Pass -1 for the default timeout.
+
+        @retval A pair (msg, pkt) where msg is a message object and pkt
+        the string representing the packet as received from the socket.
+        This allows additional parsing by the receiver if necessary.
+
+        The data members in the message are in host endian order.
+        If an error occurs, (None, None) is returned
+        """
+
+        if exp_msg is None:
+            self.logger.warn( "DEPRECATED polling for any message class" )
+            klass = None
+        elif isinstance( exp_msg, int ):
+            klass = cfg_ofp.message.message.subtypes[ exp_msg ]
+        elif issubclass( exp_msg, loxi.OFObject ):
+            klass = exp_msg
+        else:
+            raise ValueError( "Unexpected exp_msg argument %r" % exp_msg )
+
+        self.logger.debug( "Polling for %s", klass.__name__ )
+
+        # Take the packet from the queue
+        def grab( ):
+            for i, (msg, pkt) in enumerate( self.packets ):
+                if klass is None or isinstance( msg, klass ):
+                    self.logger.debug( "Got %s message",
+                                       msg.__class__.__name__ )
+                    return self.packets.pop( i )
+            # Not found
+            self.logger.debug( "%s message not in queue", klass.__name__ )
+            return None
+
+        with self.packets_cv:
+            ret = ofutils.timed_wait( self.packets_cv, grab, timeout=timeout )
+
+        if ret != None:
+            (msg, pkt) = ret
+            return (msg, pkt)
+        else:
+            return (None, None)
+
+    def transact( self, msg, timeout=-1 ):
+        """
+        Run a message transaction with the switch
+
+        Send the message in msg and wait for a reply with a matching
+        transaction id.  Transactions have the highest priority in
+        received message handling.
+
+        @param msg The message object to send; must not be a string
+        @param timeout The timeout in seconds; if -1 use default.
+        """
+
+        if msg.xid == None:
+            msg.xid = ofutils.gen_xid( )
+
+        self.logger.debug( "Running transaction %d" % msg.xid )
+
+        with self.xid_cv:
+            if self.xid:
+                self.logger.error( "Can only run one transaction at a time" )
+                return (None, None)
+
+            self.xid = msg.xid
+            self.xid_response = None
+            self.message_send( msg )
+
+            self.logger.debug( "Waiting for transaction %d" % msg.xid )
+            ofutils.timed_wait( self.xid_cv, lambda: self.xid_response,
+                                timeout=timeout )
+
+            if self.xid_response:
+                (resp, pkt) = self.xid_response
+                self.xid_response = None
+            else:
+                (resp, pkt) = (None, None)
+
+        if resp is None:
+            self.logger.warning( "No response for xid " + str( self.xid ) )
+        return (resp, pkt)
+
+    def message_send( self, msg ):
+        """
+        Send the message to the switch
+
+        @param msg A string or OpenFlow message object to be forwarded to
+        the switch.
+        """
+        if not self.switch_socket:
+            # Sending a string indicates the message is ready to go
+            raise Exception( "no socket" )
+
+        if msg.xid == None:
+            msg.xid = ofutils.gen_xid( )
+
+        outpkt = msg.pack( )
+
+        self.logger.debug( "Msg out: version %d class %s len %d xid %d",
+                           msg.version, type( msg ).__name__, len( outpkt ),
+                           msg.xid )
+
+        with self.tx_lock:
+            if self.switch_socket.sendall( outpkt ) is not None:
+                raise AssertionError( "failed to send message to switch" )
+
+        return 0  # for backwards compatibility
+
+    def clear_queue( self ):
+        """
+        Clear the input queue and report the number of messages
+        that were in it
+        """
+        enqueued_pkt_count = len( self.packets )
+        with self.packets_cv:
+            self.packets = [ ]
+        return enqueued_pkt_count
+
+    def __str__( self ):
+        string = "Controller:\n"
+        string += "  state           " + self.dbg_state + "\n"
+        string += "  switch_addr     " + str( self.switch_addr ) + "\n"
+        string += "  pending pkts    " + str( len( self.packets ) ) + "\n"
+        string += "  total pkts      " + str( self.packets_total ) + "\n"
+        string += "  expired pkts    " + str( self.packets_expired ) + "\n"
+        string += "  handled pkts    " + str( self.packets_handled ) + "\n"
+        string += "  poll discards   " + str( self.poll_discards ) + "\n"
+        string += "  parse errors    " + str( self.parse_errors ) + "\n"
+        string += "  sock errrors    " + str( self.socket_errors ) + "\n"
+        string += "  max pkts        " + str( self.max_pkts ) + "\n"
+        string += "  target switch   " + str( self.switch ) + "\n"
+        string += "  host            " + str( self.host ) + "\n"
+        string += "  port            " + str( self.port ) + "\n"
+        string += "  keep_alive      " + str( self.keep_alive ) + "\n"
+        string += "  pkt_in_run      " + str( self.pkt_in_run ) + "\n"
+        string += "  pkt_in_dropped  " + str( self.pkt_in_dropped ) + "\n"
+        return string
+
+    def show( self ):
+        print str( self )
+
+
+def sample_handler( controller, msg, pkt ):
+    """
+    Sample message handler
+
+    This is the prototype for functions registered with the controller
+    class for packet reception
+
+    @param controller The controller calling the handler
+    @param msg The parsed message object
+    @param pkt The raw packet that was received on the socket.  This is
+    in case the packet contains extra unparsed data.
+    @returns Boolean value indicating if the packet was handled.  If
+    not handled, the packet is placed in the queue for pollers to received
+    """
+    pass
diff --git a/Fabric/Utilities/src/python/oftest/dataplane.py b/Fabric/Utilities/src/python/oftest/dataplane.py
new file mode 100644
index 0000000..df79d47
--- /dev/null
+++ b/Fabric/Utilities/src/python/oftest/dataplane.py
@@ -0,0 +1,394 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
+"""
+OpenFlow Test Framework
+
+DataPlane and DataPlanePort classes
+
+Provide the interface to the control the set of ports being used
+to stimulate the switch under test.
+
+See the class dataplaneport for more details.  This class wraps
+a set of those objects allowing general calls and parsing
+configuration.
+
+@todo Add "filters" for matching packets.  Actions supported
+for filters should include a callback or a counter
+"""
+
+import sys
+import os
+import socket
+import time
+import select
+import logging
+from threading import Thread
+from threading import Lock
+from threading import Condition
+import ofutils
+import netutils
+from pcap_writer import PcapWriter
+
+if "linux" in sys.platform:
+    import afpacket
+else:
+    import pcap
+
+def match_exp_pkt(self, exp_pkt, pkt):
+    """
+    Compare the string value of pkt with the string value of exp_pkt,
+    and return True iff they are identical.  If the length of exp_pkt is
+    less than the minimum Ethernet frame size (60 bytes), then padding
+    bytes in pkt are ignored.
+    """
+    e = str(exp_pkt)
+    p = str(pkt)
+    if len(e) < 60:
+        p = p[:len(e)]
+
+    #return e == p
+    #some nic card have capature problem, will have more bytes capatured.
+    if pkt.find(exp_pkt) >=0:
+        return True
+    else:
+        if self.config["dump_packet"]:
+            self.logger.debug("rx pkt    ->"+(" ".join("{:02x}".format(ord(c)) for c in pkt)))
+            self.logger.debug("expect pkt->"+(" ".join("{:02x}".format(ord(c)) for c in exp_pkt)))
+
+        return False
+
+
+class DataPlanePortLinux:
+    """
+    Uses raw sockets to capture and send packets on a network interface.
+    """
+
+    RCV_SIZE_DEFAULT = 4096
+    ETH_P_ALL = 0x03
+    RCV_TIMEOUT = 10000
+
+    def __init__(self, interface_name, port_number):
+        """
+        @param interface_name The name of the physical interface like eth1
+        """
+        self.interface_name = interface_name
+        self.socket = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, 0)
+        afpacket.enable_auxdata(self.socket)
+        self.socket.bind((interface_name, self.ETH_P_ALL))
+        netutils.set_promisc(self.socket, interface_name)
+        self.socket.settimeout(self.RCV_TIMEOUT)
+
+    def __del__(self):
+        if self.socket:
+            self.socket.close()
+
+    def fileno(self):
+        """
+        Return an integer file descriptor that can be passed to select(2).
+        """
+        return self.socket.fileno()
+
+    def recv(self):
+        """
+        Receive a packet from this port.
+        @retval (packet data, timestamp)
+        """
+        pkt = afpacket.recv(self.socket, self.RCV_SIZE_DEFAULT)
+        return (pkt, time.time())
+
+    def send(self, packet):
+        """
+        Send a packet out this port.
+        @param packet The packet data to send to the port
+        @retval The number of bytes sent
+        """
+        return self.socket.send(packet)
+
+    def down(self):
+        """
+        Bring the physical link down.
+        """
+        #os.system("ifconfig down %s" % self.interface_name)
+        os.system("ifconfig %s down" % self.interface_name)
+
+    def up(self):
+        """
+        Bring the physical link up.
+        """
+        #os.system("ifconfig up %s" % self.interface_name)
+        os.system("ifconfig %s up" % self.interface_name)
+
+
+class DataPlanePortPcap:
+    """
+    Alternate port implementation using libpcap. This is used by non-Linux
+    operating systems.
+    """
+
+    def __init__(self, interface_name, port_number):
+        self.pcap = pcap.pcap(interface_name)
+        self.pcap.setnonblock()
+
+    def fileno(self):
+        return self.pcap.fileno()
+
+    def recv(self):
+        (timestamp, pkt) = next(self.pcap)
+        return (pkt[:], timestamp)
+
+    def send(self, packet):
+        if hasattr(self.pcap, "inject"):
+            return self.pcap.inject(packet, len(packet))
+        else:
+           return self.pcap.sendpacket(packet)
+
+    def down(self):
+        pass
+
+    def up(self):
+        pass
+
+class DataPlane(Thread):
+    """
+    This class provides methods to send and receive packets on the dataplane.
+    It uses the DataPlanePort class, or an alternative implementation of that
+    interface, to do IO on a particular port. A background thread is used to
+    read packets from the dataplane ports and enqueue them to be read by the
+    test. The kill() method must be called to shutdown this thread.
+    """
+
+    MAX_QUEUE_LEN = 100
+
+    def __init__(self, config=None):
+        Thread.__init__(self)
+
+        # dict from port number to port object
+        self.ports = {}
+
+        # dict from port number to list of (timestamp, packet)
+        self.packet_queues = {}
+
+        # cvar serves double duty as a regular top level lock and
+        # as a condition variable
+        self.cvar = Condition()
+
+        # Used to wake up the event loop from another thread
+        self.waker = ofutils.EventDescriptor()
+        self.killed = False
+
+        self.logger = logging.getLogger("dataplane")
+        self.pcap_writer = None
+
+        if config is None:
+            self.config = {}
+        else:
+            self.config = config;
+
+        ############################################################
+        #
+        # The platform/config can provide a custom DataPlanePort class
+        # here if you have a custom implementation with different
+        # behavior.
+        #
+        # Set config.dataplane.portclass = MyDataPlanePortClass
+        # where MyDataPlanePortClass has the same interface as the class
+        # DataPlanePort defined here.
+        #
+        if "dataplane" in self.config and "portclass" in self.config["dataplane"]:
+            self.dppclass = self.config["dataplane"]["portclass"]
+        elif "linux" in sys.platform:
+            self.dppclass = DataPlanePortLinux
+        else:
+            self.dppclass = DataPlanePortPcap
+
+        self.start()
+
+    def run(self):
+        """
+        Activity function for class
+        """
+        while not self.killed:
+            sockets = [self.waker] + self.ports.values()
+            try:
+                sel_in, sel_out, sel_err = select.select(sockets, [], [], 1)
+            except:
+                print sys.exc_info()
+                self.logger.error("Select error, exiting")
+                break
+
+            with self.cvar:
+                for port in sel_in:
+                    if port == self.waker:
+                        self.waker.wait()
+                        continue
+                    else:
+                        # Enqueue packet
+                        pkt, timestamp = port.recv()
+                        port_number = port._port_number
+                        self.logger.debug("Pkt len %d in on port %d",
+                                          len(pkt), port_number)
+                        if self.pcap_writer:
+                            self.pcap_writer.write(pkt, timestamp, port_number)
+                        queue = self.packet_queues[port_number]
+                        if len(queue) >= self.MAX_QUEUE_LEN:
+                            # Queue full, throw away oldest
+                            queue.pop(0)
+                            self.logger.debug("Discarding oldest packet to make room")
+                        queue.append((pkt, timestamp))
+                self.cvar.notify_all()
+
+        self.logger.info("Thread exit")
+
+    def port_add(self, interface_name, port_number):
+        """
+        Add a port to the dataplane
+        @param interface_name The name of the physical interface like eth1
+        @param port_number The port number used to refer to the port
+        Stashes the port number on the created port object.
+        """
+        self.ports[port_number] = self.dppclass(interface_name, port_number)
+        self.ports[port_number]._port_number = port_number
+        self.packet_queues[port_number] = []
+        # Need to wake up event loop to change the sockets being selected on.
+        self.waker.notify()
+
+    def send(self, port_number, packet):
+        """
+        Send a packet to the given port
+        @param port_number The port to send the data to
+        @param packet Raw packet data to send to port
+        """
+        self.logger.debug("Sending %d bytes to port %d" %
+                          (len(packet), port_number))
+        if self.pcap_writer:
+            self.pcap_writer.write(packet, time.time(), port_number)
+        bytes = self.ports[port_number].send(packet)
+        if bytes != len(packet):
+            self.logger.error("Unhandled send error, length mismatch %d != %d" %
+                     (bytes, len(packet)))
+        return bytes
+
+    def oldest_port_number(self):
+        """
+        Returns the port number with the oldest packet, or
+        None if no packets are queued.
+        """
+        min_port_number = None
+        min_time = float('inf')
+        for (port_number, queue) in self.packet_queues.items():
+            if queue and queue[0][1] < min_time:
+                min_time = queue[0][1]
+                min_port_number = port_number
+        return min_port_number
+
+    # Dequeues and yields packets in the order they were received.
+    # Yields (port number, packet, received time).
+    # If port_number is not specified yields packets from all ports.
+    def packets(self, port_number=None):
+        while True:
+            rcv_port_number = port_number or self.oldest_port_number()
+
+            if rcv_port_number == None:
+                self.logger.debug("Out of packets on all ports")
+                break
+
+            queue = self.packet_queues[rcv_port_number]
+
+            if len(queue) == 0:
+                self.logger.debug("Out of packets on port %d", rcv_port_number)
+                break
+
+            pkt, time = queue.pop(0)
+            yield (rcv_port_number, pkt, time)
+
+    def poll(self, port_number=None, timeout=-1, exp_pkt=None):
+        """
+        Poll one or all dataplane ports for a packet
+
+        If port_number is given, get the oldest packet from that port.
+        Otherwise, find the port with the oldest packet and return
+        that packet.
+
+        If exp_pkt is true, discard all packets until that one is found
+
+        @param port_number If set, get packet from this port
+        @param timeout If positive and no packet is available, block
+        until a packet is received or for this many seconds
+        @param exp_pkt If not None, look for this packet and ignore any
+        others received.  Note that if port_number is None, all packets
+        from all ports will be discarded until the exp_pkt is found
+        @return The triple port_number, packet, pkt_time where packet
+        is received from port_number at time pkt_time.  If a timeout
+        occurs, return None, None, None
+        """
+
+        if exp_pkt and not port_number:
+            self.logger.warn("Dataplane poll with exp_pkt but no port number")
+
+        # Retrieve the packet. Returns (port number, packet, time).
+        def grab():
+            self.logger.debug("Grabbing packet")
+            for (rcv_port_number, pkt, time) in self.packets(port_number):
+                self.logger.debug("Checking packet from port %d", rcv_port_number)
+                if not exp_pkt or match_exp_pkt(self, exp_pkt, pkt):
+                    return (rcv_port_number, pkt, time)
+            self.logger.debug("Did not find packet")
+            return None
+
+        with self.cvar:
+            ret = ofutils.timed_wait(self.cvar, grab, timeout=timeout)
+
+        if ret != None:
+            return ret
+        else:
+            self.logger.debug("Poll time out, no packet from " + str(port_number))
+            return (None, None, None)
+
+    def kill(self):
+        """
+        Stop the dataplane thread.
+        """
+        self.killed = True
+        self.waker.notify()
+        self.join()
+        # Explicitly release ports to ensure we don't run out of sockets
+        # even if someone keeps holding a reference to the dataplane.
+        del self.ports
+
+    def port_down(self, port_number):
+        """Brings the specified port down"""
+        self.ports[port_number].down()
+
+    def port_up(self, port_number):
+        """Brings the specified port up"""
+        self.ports[port_number].up()
+
+    def flush(self):
+        """
+        Drop any queued packets.
+        """
+        for port_number in self.packet_queues.keys():
+            self.packet_queues[port_number] = []
+
+    def start_pcap(self, filename):
+        assert(self.pcap_writer == None)
+        self.pcap_writer = PcapWriter(filename)
+
+    def stop_pcap(self):
+        if self.pcap_writer:
+            self.pcap_writer.close()
+            self.pcap_writer = None
diff --git a/Fabric/Utilities/src/python/oftest/generators.py b/Fabric/Utilities/src/python/oftest/generators.py
new file mode 100644
index 0000000..74d2ccc
--- /dev/null
+++ b/Fabric/Utilities/src/python/oftest/generators.py
@@ -0,0 +1,95 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
+from oftest.testutils import *
+from oftest import config
+import oftest.base_tests as base_tests
+import time
+
+class QinQPacketGenerator( base_tests.DataPlaneOnly ):
+    """
+    Generator for QinQ packets.
+    """
+    def runTest( self ):
+        # Get the ports and the interfaces
+        ports           = config[ "interfaces" ]
+        in_port         = ports[0][0]
+        in_interface    = ports[0][1].strip()
+        out_port        = ports[1][0]
+        out_interface   = ports[1][1].strip()
+        outer_vlan      = 20
+        innner_vlan     = 10
+        # Generating the packets
+        parsed_pkt = simple_tcp_packet_two_vlan(
+            pktlen=108,
+            out_dl_vlan_enable=True,
+            out_vlan_vid=outer_vlan,
+            in_dl_vlan_enable=True,
+            in_vlan_vid=innner_vlan
+            )
+        qinq_pkt = str( parsed_pkt )
+        # Sending the packet
+        print "\nSending packet on", out_interface
+        while True :
+            self.dataplane.send( in_port, qinq_pkt )
+            time.sleep(1)
+
+class VlanPacketGenerator( base_tests.DataPlaneOnly ):
+    """
+    Generator for vlan packets.
+    """
+    def runTest( self ):
+        # Get the ports and the interfaces
+        ports           = config[ "interfaces" ]
+        in_port         = ports[0][0]
+        in_interface    = ports[0][1].strip()
+        out_port        = ports[1][0]
+        out_interface   = ports[1][1].strip()
+        outer_vlan      = 20
+        # Generating the packets
+        parsed_pkt = simple_tcp_packet(
+            pktlen=108,
+            dl_vlan_enable=True,
+            vlan_vid=outer_vlan
+            )
+        vlan_pkt = str( parsed_pkt )
+        # Sending the packet
+        print "\nSending packet on", out_interface
+        while True :
+            self.dataplane.send( in_port, vlan_pkt )
+            time.sleep(1)
+
+class UntaggedPacketGenerator( base_tests.DataPlaneOnly ):
+    """
+    Generator for untagged packets.
+    """
+    def runTest( self ):
+        # Get the ports and the interfaces
+        ports           = config[ "interfaces" ]
+        in_port         = ports[0][0]
+        in_interface    = ports[0][1].strip()
+        out_port        = ports[1][0]
+        out_interface   = ports[1][1].strip()
+        # Generating the packets
+        parsed_pkt = simple_tcp_packet(
+            pktlen=108,
+            )
+        untagged_pkt = str( parsed_pkt )
+        # Sending the packet
+        print "\nSending packet on", out_interface
+        while True :
+            self.dataplane.send( in_port, untagged_pkt )
+            time.sleep(1)
diff --git a/Fabric/Utilities/src/python/oftest/help_formatter.py b/Fabric/Utilities/src/python/oftest/help_formatter.py
new file mode 100644
index 0000000..f877374
--- /dev/null
+++ b/Fabric/Utilities/src/python/oftest/help_formatter.py
@@ -0,0 +1,26 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
+import optparse
+
+# Don't wrap description text to give us more control over newlines.
+class HelpFormatter(optparse.IndentedHelpFormatter):
+    def format_description(self, description):
+        if description:
+            indent = " "*self.current_indent
+            return indent + description
+        else:
+            return None
diff --git a/Fabric/Utilities/src/python/oftest/illegal_message.py b/Fabric/Utilities/src/python/oftest/illegal_message.py
new file mode 100644
index 0000000..94c9572
--- /dev/null
+++ b/Fabric/Utilities/src/python/oftest/illegal_message.py
@@ -0,0 +1,59 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
+"""
+Support an illegal message
+"""
+
+import struct
+import ofp
+
+class illegal_message_type(object):
+    version = ofp.OFP_VERSION
+    type = 217
+
+    def __init__(self, xid=None):
+        self.xid = xid
+
+    def pack(self):
+        packed = []
+        packed.append(struct.pack("!B", self.version))
+        packed.append(struct.pack("!B", self.type))
+        packed.append(struct.pack("!H", 0)) # placeholder for length at index 2
+        packed.append(struct.pack("!L", self.xid))
+        length = sum([len(x) for x in packed])
+        packed[2] = struct.pack("!H", length)
+        return ''.join(packed)
+
+    @staticmethod
+    def unpack(buf):
+        raise NotImplementedError()
+
+    def __eq__(self, other):
+        if type(self) != type(other): return False
+        if self.version != other.version: return False
+        if self.type != other.type: return False
+        if self.xid != other.xid: return False
+        return True
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __str__(self):
+        return self.show()
+
+    def show(self):
+        return "illegal_message_type"
diff --git a/Fabric/Utilities/src/python/oftest/mpls.py b/Fabric/Utilities/src/python/oftest/mpls.py
new file mode 100644
index 0000000..2754e87
--- /dev/null
+++ b/Fabric/Utilities/src/python/oftest/mpls.py
@@ -0,0 +1,40 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
+# Imported from scapy at revision 1270:113ef25f9583
+# This file is not included in the Ubuntu packages of scapy, so it is checked
+# in to our repo to simplify installation. This file is under the GPLv2.
+
+# http://trac.secdev.org/scapy/ticket/31 
+
+# scapy.contrib.description = MPLS
+# scapy.contrib.status = loads
+
+from scapy.packet import Packet,bind_layers
+from scapy.fields import BitField,ByteField
+from scapy.layers.l2 import Ether
+
+class MPLS(Packet): 
+   name = "MPLS" 
+   fields_desc =  [ BitField("label", 3, 20), 
+                    BitField("cos", 0, 3), 
+                    BitField("s", 1, 1), 
+                    ByteField("ttl", 0)  ] 
+
+bind_layers(Ether, MPLS, type=0x8847)
+
+# Not in upstream scapy
+bind_layers(MPLS, MPLS, s=0)
diff --git a/Fabric/Utilities/src/python/oftest/netutils.py b/Fabric/Utilities/src/python/oftest/netutils.py
new file mode 100644
index 0000000..09a3835
--- /dev/null
+++ b/Fabric/Utilities/src/python/oftest/netutils.py
@@ -0,0 +1,77 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
+
+"""
+Network utilities for the OpenFlow test framework
+"""
+
+###########################################################################
+##                                                                         ##
+## Promiscuous mode enable/disable                                         ##
+##                                                                         ##
+## Based on code from Scapy by Phillippe Biondi                            ##
+##                                                                         ##
+##                                                                         ##
+## This program is free software; you can redistribute it and/or modify it ##
+## under the terms of the GNU General Public License as published by the   ##
+## Free Software Foundation; either version 2, or (at your option) any     ##
+## later version.                                                          ##
+##                                                                         ##
+## This program is distributed in the hope that it will be useful, but     ##
+## WITHOUT ANY WARRANTY; without even the implied warranty of              ##
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU       ##
+## General Public License for more details.                                ##
+##                                                                         ##
+#############################################################################
+
+import socket
+from fcntl import ioctl
+import struct
+
+# From net/if_arp.h
+ARPHDR_ETHER = 1
+ARPHDR_LOOPBACK = 772
+
+# From bits/ioctls.h
+SIOCGIFHWADDR  = 0x8927          # Get hardware address
+SIOCGIFINDEX   = 0x8933          # name -> if_index mapping
+
+# From netpacket/packet.h
+PACKET_ADD_MEMBERSHIP  = 1
+PACKET_DROP_MEMBERSHIP = 2
+PACKET_MR_PROMISC      = 1
+
+# From bits/socket.h
+SOL_PACKET = 263
+
+def get_if(iff,cmd):
+  s=socket.socket()
+  ifreq = ioctl(s, cmd, struct.pack("16s16x",iff))
+  s.close()
+  return ifreq
+
+def get_if_index(iff):
+  return int(struct.unpack("I",get_if(iff, SIOCGIFINDEX)[16:20])[0])
+
+def set_promisc(s,iff,val=1):
+  mreq = struct.pack("IHH8s", get_if_index(iff), PACKET_MR_PROMISC, 0, "")
+  if val:
+      cmd = PACKET_ADD_MEMBERSHIP
+  else:
+      cmd = PACKET_DROP_MEMBERSHIP
+  s.setsockopt(SOL_PACKET, cmd, mreq)
+
diff --git a/Fabric/Utilities/src/python/oftest/ofdpa_utils.py b/Fabric/Utilities/src/python/oftest/ofdpa_utils.py
new file mode 100755
index 0000000..8c97b1f
--- /dev/null
+++ b/Fabric/Utilities/src/python/oftest/ofdpa_utils.py
@@ -0,0 +1,184 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
+"""
+
+A set of inter-test utility functions for dealing with OF-DPA
+
+
+"""
+
+import logging, subprocess, time
+import ofp
+
+from oftest import config
+from oftest.testutils import *
+
+def forceOfdpaRestart( user ):
+    output = 1;
+    credential = user;
+    test = subprocess.Popen(["ssh", credential, "service ofdpa restart &> /dev/null"]);
+    time.sleep(1);
+    while output < 10:
+        output = int(subprocess.check_output(["ssh", credential, "client_cfg_purge | wc -l"]));
+        time.sleep(1);
+    subprocess.Popen(["ssh", credential, "brcm-indigo-ofdpa-ofagent -t 10.128.0.220 &> /dev/null"], stdout=subprocess.PIPE);
+
+def forceOfdpaStop( user ):
+    subprocess.Popen(["ssh", user, "ps ax | grep 'brcm-indigo-ofdpa-ofagent' | awk '{print $1}' | xargs sudo kill"], stdout=subprocess.PIPE);
+
+
+class table(object):
+    """ Metadata on each OFDPA table """
+    def __init__(self, table_id, table_name):
+        self.table_id = table_id
+        self.table_name = table_name
+    # TODO consider adding type checking verification here
+
+INGRESS_TABLE           = table(0, "Ingress")
+VLAN_TABLE              = table(10, "VLAN")
+MACTERM_TABLE           = table(20, "MacTerm")
+UNICAST_ROUTING_TABLE   = table(30, "Unicast Routing")
+MULTICAST_ROUTING_TABLE = table(40, "Multicast Routing")
+BRIDGING_TABLE          = table(50, "Bridging Table")
+ACL_TABLE               = table(60, "ACL Policy Table")
+#.... FIXME add all tables
+
+DEFAULT_VLAN = 1
+
+def enableVlanOnPort(controller, vlan, port=ofp.OFPP_ALL, priority=0):
+    if port == ofp.OFPP_ALL:
+        ports = sorted(config["port_map"].keys())
+    else:
+        ports = [port]
+    for port in ports:
+        tagged_match = ofp.match([
+                ofp.oxm.in_port(port),
+                ofp.oxm.vlan_vid(vlan | ofp.OFPVID_PRESENT)
+                ])
+
+        request = ofp.message.flow_add(
+            table_id = VLAN_TABLE.table_id,
+            cookie = 0xdead,
+            match = tagged_match,
+            instructions = [
+                ofp.instruction.goto_table(MACTERM_TABLE.table_id),
+#                 ofp.instruction.apply_actions(
+#                     actions=[
+#                         ofp.action.push_vlan(ethertype=0x8100), # DO NOT PUT THIS FOR OF-DPA 2.0 EA1 - seems to not matter for EA2
+#                         ofp.action.set_field(ofp.oxm.vlan_vid( ofp.OFPVID_PRESENT | vlan))
+#                     ]),
+                    ],
+            buffer_id = ofp.OFP_NO_BUFFER,
+            priority = priority)
+
+        logging.info("Inserting vlan rule allowing tagged vlan %d on port %d" % (vlan, port))
+        controller.message_send(request)
+        do_barrier(controller)
+        verify_no_errors(controller)
+
+
+def installDefaultVlan(controller, vlan=DEFAULT_VLAN, port=ofp.OFPP_ALL, priority=0):
+    """ Insert a rule that maps all untagged traffic to vlan $vlan
+
+    In OFDPA, table 10 (the vlan table) requires that all traffic be
+    mapped to an internal vlan else the packets be dropped.  This function
+    sets up a default vlan mapping all untagged traffic to an internal VLAN.
+
+    With OF-DPA, before you can insert a 'untagged to X' rule on a
+    port, you must first insert a 'X --> X' rule for the same port.
+
+    Further, the 'X --> X' rule must set ofp.OFPVID_PRESENT even
+    though 'X' is non-zero.
+
+    The 'controller' variable is self.controller from a test
+    """
+    # OFDPA seems to be dumb and wants each port set individually
+    #       Can't set all ports by using OFPP_ALL
+    if port == ofp.OFPP_ALL:
+        ports = sorted(config["port_map"].keys())
+    else:
+        ports = [port]
+
+    for port in ports:
+        # enable this vlan on this port before we can map untagged packets to the vlan
+        enableVlanOnPort(controller, vlan, port)
+
+        untagged_match = ofp.match([
+                ofp.oxm.in_port(port),
+                # OFDPA 2.0 says untagged is vlan_id=0, mask=ofp.OFPVID_PRESENT
+                ofp.oxm.vlan_vid_masked(0,ofp.OFPVID_PRESENT)    # WTF OFDPA 2.0EA2 -- really!?
+                ])
+
+        request = ofp.message.flow_add(
+            table_id = VLAN_TABLE.table_id,
+            cookie = 0xbeef,
+            match = untagged_match,
+            instructions = [
+                ofp.instruction.apply_actions(
+                    actions=[
+                        #ofp.action.push_vlan(ethertype=0x8100),
+                        ofp.action.set_field(ofp.oxm.vlan_vid(ofp.OFPVID_PRESENT | vlan))
+                    ]),
+                ofp.instruction.goto_table(MACTERM_TABLE.table_id)   
+                    ],
+            buffer_id = ofp.OFP_NO_BUFFER,
+            priority = priority)
+
+        logging.info("Inserting default vlan sending all untagged traffic to vlan %d on port %d" % (vlan, port))
+        controller.message_send(request)
+        do_barrier(controller)
+        verify_no_errors(controller)
+
+
+_group_types = {
+    "L2 Interface": 0,
+    "L2 Rewrite" : 1,
+    "L3 Unicast" : 2,
+    "L2 Multicast" : 3,
+    "L2 Flood" : 4,
+    "L3 Interface" : 5,
+    "L3 Multicast": 6,
+    "L3 ECMP": 7,
+    "L2 Data Center Overlay": 8,
+    "MPLS Label" : 9,
+    "MPLS Forwarding" :10,
+    "L2 Unfiltered Interface": 11,
+    "L2 Loopback": 12,
+}
+
+
+def makeGroupID(groupType, local_id):
+    """ Group IDs in OF-DPA have rich meaning
+
+    @param groupType is a key in _group_types
+    @param local_id is an integer 0<= local_id < 2**27,
+        but it may have more semantic meaning depending on the
+        groupType
+
+
+    Read Section 4.3 of the OF-DPA manual on groups for 
+    details
+    """
+    if groupType not in _group_types:
+        raise KeyError("%s not a valid OF-DPA group type" % groupType)
+    if local_id < 0 or local_id >=134217728:
+        raise ValueError("local_id %d must be  0<= local_id < 2**27" % local_id)
+    return (_group_types[groupType] << 28) + local_id
+
+
+def delete_all_recursive_groups(controller):
+    pass
diff --git a/Fabric/Utilities/src/python/oftest/oft12/__init__.py b/Fabric/Utilities/src/python/oftest/oft12/__init__.py
new file mode 100644
index 0000000..d4e8062
--- /dev/null
+++ b/Fabric/Utilities/src/python/oftest/oft12/__init__.py
@@ -0,0 +1,16 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
diff --git a/Fabric/Utilities/src/python/oftest/oft12/packet.py b/Fabric/Utilities/src/python/oftest/oft12/packet.py
new file mode 100644
index 0000000..55724d6
--- /dev/null
+++ b/Fabric/Utilities/src/python/oftest/oft12/packet.py
@@ -0,0 +1,1315 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
+######################################################################
+#
+# 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()
+
diff --git a/Fabric/Utilities/src/python/oftest/oft12/testutils.py b/Fabric/Utilities/src/python/oftest/oft12/testutils.py
new file mode 100644
index 0000000..3ce2e75
--- /dev/null
+++ b/Fabric/Utilities/src/python/oftest/oft12/testutils.py
@@ -0,0 +1,1518 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
+
+import sys
+import logging
+from cStringIO import StringIO
+#import types
+
+import ofp
+import oftest.parse as parse
+from packet import Packet
+
+try:
+    logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
+    from scapy.all import *
+    from oftest.mpls import *
+except ImportError:
+    sys.exit("Need to install scapy for packet parsing")
+
+global skipped_test_count
+skipped_test_count = 0
+
+# Some useful defines
+IP_ETHERTYPE = 0x800
+IPV6_ETHERTYPE = 0x86dd
+ETHERTYPE_VLAN = 0x8100
+ETHERTYPE_MPLS = 0x8847
+TCP_PROTOCOL = 0x6
+UDP_PROTOCOL = 0x11
+ICMPV6_PROTOCOL = 0x3a
+
+
+
+def clear_switch(parent, port_list, logger):
+    """
+    Clear the switch configuration
+
+    @param parent Object implementing controller and assert equal
+    @param logger Logging object
+    """
+    parent.assertTrue(len(port_list) > 2, "Not enough ports for test")
+    for port in port_list:
+        clear_port_config(parent, port, logger)
+    initialize_table_config(parent.controller, logger)
+    delete_all_flows(parent.controller, logger)
+    delete_all_groups(parent.controller, logger)
+
+    return port_list
+
+def initialize_table_config(ctrl, logger):
+    """
+    Initialize all table configs to default setting ("CONTROLLER")
+    @param ctrl The controller object for the test
+    """
+    logger.info("Initializing all table configs")
+    request = ofp.message.table_mod()  
+    request.config = ofp.OFPTC_TABLE_MISS_CONTROLLER
+    rv = 0
+    for table_id in [0, 1, 2, 3, 4, 5, 6, 7]:
+        request.table_id = table_id
+        rv |= ctrl.message_send(request)
+    return rv
+
+def delete_all_flows(ctrl, logger):
+    """
+    Delete all flows on the switch
+    @param ctrl The controller object for the test
+    @param logger Logging object
+    """
+
+    logger.info("Deleting all flows")
+    #DEFAULT_TABLE_COUNT = 4
+    return delete_all_flows_one_table(ctrl, logger, table_id=0xff)
+
+def delete_all_flows_one_table(ctrl, logger, table_id=0):
+    """
+    Delete all flows on a table
+    @param ctrl The controller object for the test
+    @param logger Logging object
+    @param table_id Table ID
+    """
+    logger.info("Deleting all flows on table ID: " + str(table_id))
+    msg = ofp.message.flow_delete()
+    msg.out_port = ofp.OFPP_ANY
+    msg.out_group = ofp.OFPG_ANY
+    msg.buffer_id = 0xffffffff
+    msg.table_id = table_id
+    logger.debug(msg.show())
+
+    return ctrl.message_send(msg)
+
+def delete_all_groups(ctrl, logger):
+    """
+    Delete all groups on the switch
+    @param ctrl The controller object for the test
+    @param logger Logging object
+    """
+    
+    logger.info("Deleting all groups")
+    msg = ofp.message.group_mod()
+    msg.group_id = ofp.OFPG_ALL
+    msg.command = ofp.OFPGC_DELETE
+    logger.debug(msg.show())
+    return ctrl.message_send(msg)
+
+def clear_port_config(parent, port, logger):
+    """
+    Clear the port configuration 
+
+    @param parent Object implementing controller and assert equal
+    @param logger Logging object
+    """
+    rv = port_config_set(parent.controller, port,
+                         0, 0, logger)
+    parent.assertEqual(rv, 0, "Failed to reset port config")
+
+def simple_tcp_packet(dl_dst='00:01:02:03:04:05',
+                      dl_src='00:06:07:08:09:0a',
+                      vlan_tags=[],  # {type,vid,pcp,cfi}  TODO type
+                      mpls_tags=[],  # {type,label,tc,ttl} TODO type 
+                      ip_src='192.168.0.1',
+                      ip_dst='192.168.0.2',
+                      ip_tos=0,
+                      ip_ttl=64,
+                      tcp_sport=1234,
+                      tcp_dport=80,
+                      payload_len = 46):
+    pkt = Ether(dst=dl_dst, src=dl_src)
+
+    vlans_num = 0
+    while len(vlan_tags):
+        tag = vlan_tags.pop(0)
+        dot1q = Dot1Q()
+        if 'vid' in tag:
+            dot1q.vlan = tag['vid']
+        if 'pcp' in tag:
+            dot1q.prio = tag['pcp']
+        if 'cfi' in tag:
+            dot1q.id = tag['cfi']
+        pkt = pkt / dot1q 
+        if 'type' in tag:
+            if vlans_num == 0:
+                pkt[Ether].setfieldval('type', tag['type'])
+            else:
+                pkt[Dot1Q:vlans_num].setfieldval('type', tag['type'])
+        vlans_num+=1
+
+    mplss_num = 0
+    while len(mpls_tags):
+        tag = mpls_tags.pop(0)
+        mpls = MPLS()
+        if 'label' in tag:
+            mpls.label = tag['label']
+        if 'tc' in tag:
+            mpls.cos = tag['tc']
+        if 'ttl' in tag:
+            mpls.ttl = tag['ttl']
+        pkt = pkt / mpls
+        if 'type' in tag:
+            if mplss_num == 0:
+                if vlans_num == 0:
+                    pkt[Ether].setfieldval('type', tag['type'])
+                else:
+                    pkt[Dot1Q:vlans_num].setfieldval('type', tag['type'])
+        mplss_num+=1
+
+    pkt = pkt / IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl) \
+              / TCP(sport=tcp_sport, dport=tcp_dport)
+    
+    pkt = pkt / ("D" * payload_len)
+
+    return pkt
+
+def simple_icmp_packet(dl_dst='00:01:02:03:04:05',
+                       dl_src='00:06:07:08:09:0a',
+                       vlan_tags=[],  # {type,vid,pcp,cfi}  TODO type
+                       mpls_tags=[],  # {type,label,tc,ttl} TODO type 
+                       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,
+                       payload_len=0):
+
+    #TODO simple_ip_packet
+    pkt = Ether(dst=dl_dst, src=dl_src)
+
+    vlans_num = 0
+    while len(vlan_tags):
+        tag = vlan_tags.pop(0)
+        dot1q = Dot1Q()
+        if 'vid' in tag:
+            dot1q.vlan = tag['vid']
+        if 'pcp' in tag:
+            dot1q.prio = tag['pcp']
+        if 'cfi' in tag:
+            dot1q.id = tag['cfi']
+        pkt = pkt / dot1q 
+        if 'type' in tag:
+            if vlans_num == 0:
+                pkt[Ether].setfieldval('type', tag['type'])
+            else:
+                pkt[Dot1Q:vlans_num].setfieldval('type', tag['type'])
+        vlans_num+=1
+
+    mplss_num = 0
+    while len(mpls_tags):
+        tag = mpls_tags.pop(0)
+        mpls = MPLS()
+        if 'label' in tag:
+            mpls.label = tag['label']
+        if 'tc' in tag:
+            mpls.cos = tag['tc']
+        if 'ttl' in tag:
+            mpls.ttl = tag['ttl']
+        pkt = pkt / mpls
+        if 'type' in tag:
+            if mplss_num == 0:
+                if vlans_num == 0:
+                    pkt[Ether].setfieldval('type', tag['type'])
+                else:
+                    pkt[Dot1Q:vlans_num].setfieldval('type', tag['type'])
+        mplss_num+=1
+
+    pkt = pkt / IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl) \
+              / ICMP(type=icmp_type, code=icmp_code)
+
+    pkt = pkt / ("D" * payload_len)
+
+    return pkt
+
+def simple_ipv6_packet(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='fe80::2420:52ff:fe8f:5189',
+                      ip_dst='fe80::2420:52ff:fe8f:5190',
+                      ip_tos=0,
+                      tcp_sport=0,
+                      tcp_dport=0, 
+                      EH = False, 
+                      EHpkt = IPv6ExtHdrDestOpt()
+                      ):
+
+    """
+    Return a simple IPv6 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 VLAN ID
+    @param dl_vlan_pcp VLAN priority
+    @param ip_src IPv6 source
+    @param ip_dst IPv6 destination
+    @param ip_tos IP ToS
+    @param tcp_dport TCP destination port
+    @param ip_sport TCP source port
+
+    """
+    # Note Dot1Q.id is really CFI
+    if (dl_vlan_enable):
+        pkt = Ether(dst=dl_dst, src=dl_src)/ \
+            Dot1Q(prio=dl_vlan_pcp, id=dl_vlan_cfi, vlan=dl_vlan)/ \
+            IPv6(src=ip_src, dst=ip_dst)
+
+    else:
+        pkt = Ether(dst=dl_dst, src=dl_src)/ \
+            IPv6(src=ip_src, dst=ip_dst)
+
+    # Add IPv6 Extension Headers 
+    if EH:
+        pkt = pkt / EHpkt
+
+    if (tcp_sport >0 and tcp_dport >0):
+        pkt = pkt / TCP(sport=tcp_sport, dport=tcp_dport)
+
+    if pktlen > len(pkt) :
+        pkt = pkt/("D" * (pktlen - len(pkt)))
+
+    return pkt
+
+def simple_icmpv6_packet(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='fe80::2420:52ff:fe8f:5189',
+                      ip_dst='fe80::2420:52ff:fe8f:5190',
+                      ip_tos=0,
+                      tcp_sport=0,
+                      tcp_dport=0, 
+                      EH = False, 
+                      EHpkt = IPv6ExtHdrDestOpt(),
+                      route_adv = False,
+                      sll_enabled = False
+                      ):
+
+    """
+    Return a simple dataplane ICMPv6 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 VLAN ID
+    @param dl_vlan_pcp VLAN priority
+    @param ip_src IPv6 source
+    @param ip_dst IPv6 destination
+    @param ip_tos IP ToS
+    @param tcp_dport TCP destination port
+    @param ip_sport TCP source port
+    
+    """
+    if (dl_vlan_enable):
+        pkt = Ether(dst=dl_dst, src=dl_src)/ \
+            Dot1Q(prio=dl_vlan_pcp, id=dl_vlan_cfi, vlan=dl_vlan)/ \
+            IPv6(src=ip_src, dst=ip_dst)
+
+    else:
+        pkt = Ether(dst=dl_dst, src=dl_src)/ \
+            IPv6(src=ip_src, dst=ip_dst)
+            
+            
+    # Add IPv6 Extension Headers 
+    if EH:
+        pkt = pkt / EHpkt
+
+    if route_adv:
+        pkt = pkt/ \
+        ICMPv6ND_RA(chlim=255, H=0L, M=0L, O=1L, routerlifetime=1800, P=0L, retranstimer=0, prf=0L, res=0L)/ \
+        ICMPv6NDOptPrefixInfo(A=1L, res2=0, res1=0L, L=1L, len=4, prefix='fd00:141:64:1::', R=0L, validlifetime=1814400, prefixlen=64, preferredlifetime=604800, type=3)
+        if sll_enabled :
+            pkt = pkt/ \
+            ICMPv6NDOptSrcLLAddr(type=1, len=1, lladdr='66:6f:df:2d:7c:9c')
+    else :
+        pkt = pkt/ \
+            ICMPv6EchoRequest()
+    if (tcp_sport >0 and tcp_dport >0):
+        pkt = pkt / TCP(sport=tcp_sport, dport=tcp_dport)
+
+    if pktlen > len(pkt) :
+        pkt = pkt/("D" * (pktlen - len(pkt)))
+
+    return pkt
+
+
+def do_barrier(ctrl):
+    b = ofp.message.barrier_request()
+    ctrl.transact(b)
+
+
+def port_config_get(controller, port_no, logger):
+    """
+    Get a port's configuration
+
+    Gets the switch feature configuration and grabs one port's
+    configuration
+
+    @returns (hwaddr, config, advert) The hwaddress, configuration and
+    advertised values
+    """
+    request = ofp.message.features_request()
+    reply, _ = controller.transact(request, timeout=2)
+    if reply is None:
+        logger.warn("Get feature request failed")
+        return None, None, None
+    logger.debug(reply.show())
+    for idx in range(len(reply.ports)):
+        if reply.ports[idx].port_no == port_no:
+            return (reply.ports[idx].hw_addr, reply.ports[idx].config,
+                    reply.ports[idx].advertised)
+    
+    logger.warn("Did not find port number for port config")
+    return None, None, None
+
+def port_config_set(controller, port_no, config, mask, logger):
+    """
+    Set the port configuration according the given parameters
+
+    Gets the switch feature configuration and updates one port's
+    configuration value according to config and mask
+    """
+    logger.info("Setting port " + str(port_no) + " to config " + str(config))
+    request = ofp.message.features_request()
+    reply, _ = controller.transact(request, timeout=2)
+    if reply is None:
+        return -1
+    logger.debug(reply.show())
+    for idx in range(len(reply.ports)):
+        if reply.ports[idx].port_no == port_no:
+            break
+    if idx >= len(reply.ports):
+        return -1
+    mod = ofp.message.port_mod()
+    mod.port_no = port_no
+    mod.hw_addr = reply.ports[idx].hw_addr
+    mod.config = config
+    mod.mask = mask
+    mod.advertise = reply.ports[idx].advertised
+    rv = controller.message_send(mod)
+    return rv
+
+def receive_pkt_check(dataplane, pkt, yes_ports, no_ports, assert_if, logger):
+    """
+    Check for proper receive packets across all ports
+    @param dataplane The dataplane object
+    @param pkt Expected packet; may be None if yes_ports is empty
+    @param yes_ports Set or list of ports that should recieve packet
+    @param no_ports Set or list of ports that should not receive packet
+    @param assert_if Object that implements assertXXX
+    """
+    for ofport in yes_ports:
+        logger.debug("Checking for pkt on port " + str(ofport))
+        (_, rcv_pkt, _) = dataplane.poll(
+            port_number=ofport, timeout=1)
+        assert_if.assertTrue(rcv_pkt is not None, 
+                             "Did not receive pkt on " + str(ofport))
+        assert_if.assertEqual(str(pkt), str(rcv_pkt),
+                              "Response packet does not match send packet " +
+                              "on port " + str(ofport))
+
+    for ofport in no_ports:
+        logger.debug("Negative check for pkt on port " + str(ofport))
+        (_, rcv_pkt, _) = dataplane.poll(
+            port_number=ofport, timeout=1)
+        assert_if.assertTrue(rcv_pkt is None, 
+                             "Unexpected pkt on port " + str(ofport))
+
+
+def pkt_verify(parent, rcv_pkt, exp_pkt):
+    if str(exp_pkt) != str(rcv_pkt):
+        logging.error("ERROR: Packet match failed.")
+        logging.debug("Expected (" + str(len(exp_pkt)) + ")")
+        logging.debug(str(exp_pkt).encode('hex'))
+        sys.stdout = tmpout = StringIO()
+        exp_pkt.show()
+        sys.stdout = sys.__stdout__
+        logging.debug(tmpout.getvalue())
+        logging.debug("Received (" + str(len(rcv_pkt)) + ")")
+        logging.debug(str(rcv_pkt).encode('hex'))
+        sys.stdout = tmpout = StringIO()
+        Ether(rcv_pkt).show()
+        sys.stdout = sys.__stdout__
+        logging.debug(tmpout.getvalue())
+    parent.assertEqual(str(exp_pkt), str(rcv_pkt),
+                       "Packet match error")
+    
+    return rcv_pkt
+
+def receive_pkt_verify(parent, egr_port, exp_pkt):
+    """
+    Receive a packet and verify it matches an expected value
+
+    parent must implement dataplane, assertTrue and assertEqual
+    """
+    (rcv_port, rcv_pkt, _) = parent.dataplane.poll(port_number=egr_port,
+                                                          timeout=1)
+
+    if exp_pkt is None:
+        if rcv_pkt is None:
+            return None
+        else:
+            logging.error("ERROR: Received unexpected packet from " + str(egr_port));
+            return rcv_pkt
+
+    if rcv_pkt is None:
+        logging.error("ERROR: No packet received from " + str(egr_port))
+
+    parent.assertTrue(rcv_pkt is not None,
+                      "Did not receive packet port " + str(egr_port))
+    logging.debug("Packet len " + str(len(rcv_pkt)) + " in on " + 
+                    str(rcv_port))
+
+    return pkt_verify(parent, rcv_pkt, exp_pkt)
+
+def packetin_verify(parent, exp_pkt):
+    """
+    Receive packet_in and verify it matches an expected value
+    """
+    (response, _) = parent.controller.poll(ofp.OFPT_PACKET_IN, 2)
+
+    parent.assertTrue(response is not None, 'Packet in ofp.message not received')
+    if str(exp_pkt) != response.data:
+        logging.debug("pkt  len " + str(len(str(exp_pkt))) + ": "
+                            + str(exp_pkt).encode('hex'))
+        logging.debug("resp len " + str(len(str(response.data))) + ": "
+                            + str(response.data).encode('hex'))
+    parent.assertEqual(str(exp_pkt), response.data,
+                     'PACKET_IN packet does not match send packet')
+
+def match_verify(parent, req_match, res_match):
+    """
+    Verify flow matches agree; if they disagree, report where
+
+    parent must implement assertEqual
+    Use str() to ensure content is compared and not pointers
+    """
+
+    parent.assertEqual(req_match.wildcards, res_match.wildcards,
+                       'Match failed: wildcards: ' + hex(req_match.wildcards) +
+                       " != " + hex(res_match.wildcards))
+    parent.assertEqual(req_match.in_port, res_match.in_port,
+                       'Match failed: in_port: ' + str(req_match.in_port) +
+                       " != " + str(res_match.in_port))
+    parent.assertEqual(str(req_match.dl_src), str(res_match.dl_src),
+                       'Match failed: dl_src: ' + str(req_match.dl_src) +
+                       " != " + str(res_match.dl_src))
+    parent.assertEqual(str(req_match.dl_dst), str(res_match.dl_dst),
+                       'Match failed: dl_dst: ' + str(req_match.dl_dst) +
+                       " != " + str(res_match.dl_dst))
+    parent.assertEqual(req_match.dl_vlan, res_match.dl_vlan,
+                       'Match failed: dl_vlan: ' + str(req_match.dl_vlan) +
+                       " != " + str(res_match.dl_vlan))
+    parent.assertEqual(req_match.dl_vlan_pcp, res_match.dl_vlan_pcp,
+                       'Match failed: dl_vlan_pcp: ' + 
+                       str(req_match.dl_vlan_pcp) + " != " + 
+                       str(res_match.dl_vlan_pcp))
+    parent.assertEqual(req_match.dl_type, res_match.dl_type,
+                       'Match failed: dl_type: ' + str(req_match.dl_type) +
+                       " != " + str(res_match.dl_type))
+
+    if (not(req_match.wildcards & ofp.OFPFW_DL_TYPE)
+        and (req_match.dl_type == IP_ETHERTYPE)):
+        parent.assertEqual(req_match.nw_tos, res_match.nw_tos,
+                           'Match failed: nw_tos: ' + str(req_match.nw_tos) +
+                           " != " + str(res_match.nw_tos))
+        parent.assertEqual(req_match.nw_proto, res_match.nw_proto,
+                           'Match failed: nw_proto: ' + str(req_match.nw_proto) +
+                           " != " + str(res_match.nw_proto))
+        parent.assertEqual(req_match.nw_src, res_match.nw_src,
+                           'Match failed: nw_src: ' + str(req_match.nw_src) +
+                           " != " + str(res_match.nw_src))
+        parent.assertEqual(req_match.nw_dst, res_match.nw_dst,
+                           'Match failed: nw_dst: ' + str(req_match.nw_dst) +
+                           " != " + str(res_match.nw_dst))
+
+        if (not(req_match.wildcards & ofp.OFPFW_NW_PROTO)
+            and ((req_match.nw_proto == TCP_PROTOCOL)
+                 or (req_match.nw_proto == UDP_PROTOCOL))):
+            parent.assertEqual(req_match.tp_src, res_match.tp_src,
+                               'Match failed: tp_src: ' + 
+                               str(req_match.tp_src) +
+                               " != " + str(res_match.tp_src))
+            parent.assertEqual(req_match.tp_dst, res_match.tp_dst,
+                               'Match failed: tp_dst: ' + 
+                               str(req_match.tp_dst) +
+                               " != " + str(res_match.tp_dst))
+
+def flow_removed_verify(parent, request=None, pkt_count=-1, byte_count=-1):
+    """
+    Receive a flow removed msg and verify it matches expected
+
+    @params parent Must implement controller, assertEqual
+    @param pkt_count If >= 0, verify packet count
+    @param byte_count If >= 0, verify byte count
+    """
+    (response, _) = parent.controller.poll(ofp.OFPT_FLOW_REMOVED, 2)
+    parent.assertTrue(response is not None, 'No flow removed ofp.message received')
+
+    if request is None:
+        return
+
+    parent.assertEqual(request.cookie, response.cookie,
+                       "Flow removed cookie error: " +
+                       hex(request.cookie) + " != " + hex(response.cookie))
+
+    req_match = request.match
+    res_match = response.match
+    verifyMatchField(req_match, res_match)
+
+    if (req_match.wildcards != 0):
+        parent.assertEqual(request.priority, response.priority,
+                           'Flow remove prio mismatch: ' + 
+                           str(request.priority) + " != " + 
+                           str(response.priority))
+        parent.assertEqual(response.reason, ofp.OFPRR_HARD_TIMEOUT,
+                           'Flow remove reason is not HARD TIMEOUT:' +
+                           str(response.reason))
+        if pkt_count >= 0:
+            parent.assertEqual(response.packet_count, pkt_count,
+                               'Flow removed failed, packet count: ' + 
+                               str(response.packet_count) + " != " +
+                               str(pkt_count))
+        if byte_count >= 0:
+            parent.assertEqual(response.byte_count, byte_count,
+                               'Flow removed failed, byte count: ' + 
+                               str(response.byte_count) + " != " + 
+                               str(byte_count))
+def flow_msg_create(parent, pkt, ing_port=0, match_fields=None, instruction_list=None, 
+                    action_list=None,wildcards=0, egr_port=None, 
+                    egr_queue=None, table_id=0, check_expire=False):
+    """
+    Multi-purpose flow_mod creation utility
+
+    Match on packet with given wildcards.  
+    See flow_match_test for other parameter descriptoins
+   
+    if egr_queue is set
+             append an out_queue ofp.action to egr_queue to the actions_list
+    else if egr_port is set:  
+             append an output ofp.action to egr_port to the actions_list
+    if the instruction_list is empty, 
+        append an APPLY ofp.instruction to it
+    Add the action_list to the first write or apply ofp.instruction
+    
+    @param egr_queue if not None, make the output an enqueue ofp.action
+    @param table_id Table ID for writing a flow_mod
+    """
+
+    if match_fields is None:
+        match_fields = parse.packet_to_flow_match(pkt)
+    parent.assertTrue(match_fields is not None, "Flow match from pkt failed")
+    in_port = ofp.oxm.in_port(ing_port)
+    match_fields.oxm_list.append(in_port) 
+    request = ofp.message.flow_add()
+    request.match= match_fields
+    request.buffer_id = 0xffffffff
+    request.table_id = table_id
+    
+    if check_expire:
+        request.flags |= ofp.OFPFF_SEND_FLOW_REM
+        request.hard_timeout = 1    
+    
+    if action_list is None:
+        action_list = []
+    if instruction_list is None:
+        instruction_list = []
+    
+    # Set up output/enqueue ofp.action if directed
+    if egr_queue is not None:
+        parent.assertTrue(egr_port is not None, "Egress port not set")
+        act = ofp.action.set_queue()
+        act.port = egr_port
+        act.queue_id = egr_queue
+        action_list.append(act)
+    elif egr_port is not None:
+        act = ofp.action.output()
+        act.port = egr_port
+        action_list.append(act)
+        
+    inst = None
+    if len(instruction_list) == 0: 
+        inst = ofp.instruction.apply_actions()
+        instruction_list.append(inst)
+    else:
+        for inst in instruction_list:
+            if (inst.type == ofp.OFPIT_WRITE_ACTIONS or
+                inst.type == ofp.OFPIT_APPLY_ACTIONS):
+                break
+
+
+    # add all the actions to the last inst
+    inst.actions += action_list
+
+    # add all the instrutions to the flow_mod
+    request.instructions += instruction_list
+ 
+    logging.debug(request.show())
+    return request
+
+def flow_msg_install(parent, request, clear_table=True):
+    """
+    Install a flow mod ofp.message in the switch
+
+    @param parent Must implement controller, assertEqual, assertTrue
+    @param request The request, all set to go
+    @param clear_table If true, clear the flow table before installing
+    """
+    if clear_table:
+        logging.debug("Clear flow table")
+        if request.table_id:
+            table_id = request.table_id
+        else:
+            table_id = 0
+        rc = delete_all_flows_one_table(parent.controller,
+                                        logging,
+                                        table_id)
+        parent.assertEqual(rc, 0, "Failed to delete all flows on table: "
+                           + str(table_id))
+        do_barrier(parent.controller)
+
+    logging.debug("Insert flow::\n%s" % request.show())
+    rv = parent.controller.message_send(request)
+    parent.assertTrue(rv != -1, "Error installing flow mod")
+    do_barrier(parent.controller)
+
+def error_verify(parent, exp_type, exp_code):
+    """
+    Receive an error msg and verify if it is as expected
+
+    @param parent Must implement controller, assertEqual
+    @param exp_type Expected error type
+    @param exp_code Expected error code
+    """
+    (response, raw) = parent.controller.poll(ofp.OFPT_ERROR, 2)
+    parent.assertTrue(response is not None, 'No error ofp.message received')
+
+    if (exp_type is None) or (exp_code is None):
+        logging.debug("Parametrs are not sufficient")
+        return
+
+    parent.assertEqual(exp_type, response.type,
+                       'Error ofp.message type mismatch: ' +
+                       str(exp_type) + " != " +
+                       str(response.type))
+    parent.assertEqual(exp_code, response.code,
+                       'Error ofp.message code mismatch: ' +
+                       str(exp_code) + " != " +
+                       str(response.code))
+
+def flow_match_test_port_pair(parent, ing_port, egr_port, match=None, 
+                              wildcards=0, mask=None,
+                              dl_vlan=-1, pkt=None, exp_pkt=None,
+                              apply_action_list=None, check_expire=False):
+    """
+    Flow match test on single TCP packet
+
+    Run test with packet through switch from ing_port to egr_port
+    See flow_match_test for parameter descriptions
+    """
+
+    logging.info("Pkt match test: " + str(ing_port) + " to " + str(egr_port))
+    logging.debug("  WC: " + hex(wildcards) + " vlan: " + str(dl_vlan) +
+                    " expire: " + str(check_expire))
+    if pkt is None:
+        if dl_vlan >= 0:
+            pkt = simple_tcp_packet(vlan_tags=[{'vid': dl_vlan}])
+        else:
+            pkt = simple_tcp_packet()
+
+    match = parse.packet_to_flow_match(pkt)
+    parent.assertTrue(match is not None, "Flow match from pkt failed")
+
+    if mask is not None:
+        match.dl_src_mask = mask['dl_src']
+        match.dl_dst_mask = mask['dl_dst']
+        match.nw_src_mask = mask['nw_src']
+        match.nw_dst_mask = mask['nw_dst']
+        #Set unmatching values on corresponding match fields
+        for i in range(ofp.OFP_ETH_ALEN):
+            match.dl_src[i] = match.dl_src[i] ^ match.dl_src_mask[i]
+            match.dl_dst[i] = match.dl_dst[i] ^ match.dl_dst_mask[i]
+        match.nw_src = match.nw_src ^ match.nw_src_mask
+        match.nw_dst = match.nw_dst ^ match.nw_dst_mask
+
+    request = flow_msg_create(parent, pkt, ing_port=ing_port, 
+                              match=match,
+                              wildcards=wildcards, egr_port=egr_port,
+                              action_list=apply_action_list)
+
+    flow_msg_install(parent, request)
+
+    logging.debug("Send packet: " + str(ing_port) + " to " + str(egr_port))
+    parent.dataplane.send(ing_port, str(pkt))
+
+    if exp_pkt is None:
+        exp_pkt = pkt
+    receive_pkt_verify(parent, egr_port, exp_pkt)
+
+    if check_expire:
+        #@todo Not all HW supports both pkt and byte counters
+        flow_removed_verify(parent, request, pkt_count=1, byte_count=len(pkt))
+def flow_match_test(parent, port_map, match=None, wildcards=0,
+                    mask=None, dl_vlan=-1, pkt=None,
+                    exp_pkt=None, apply_action_list=None,
+                    check_expire=False,  max_test=0):
+    """
+    Run flow_match_test_port_pair on all port pairs
+
+    @param max_test If > 0 no more than this number of tests are executed.
+    @param parent Must implement controller, dataplane, assertTrue, assertEqual
+    and logger
+    @param pkt If not None, use this packet for ingress
+    @param match If not None, use this value in flow_mod
+    @param wildcards For flow match entry
+    @param mask DL/NW address bit masks as a dictionary. If set, it is tested
+    against the corresponding match fields with the opposite values
+    @param dl_vlan If not -1, and pkt is None, create a pkt w/ VLAN tag
+    @param exp_pkt If not None, use this as the expected output pkt; els use pkt
+    @param action_list Additional actions to add to flow mod
+    @param check_expire Check for flow expiration ofp.message
+    """
+    of_ports = port_map.keys()
+    of_ports.sort()
+    parent.assertTrue(len(of_ports) > 1, "Not enough ports for test")
+    test_count = 0
+
+    for ing_idx in range(len(of_ports)):
+        ingress_port = of_ports[ing_idx]
+        for egr_idx in range(len(of_ports)):
+            if egr_idx == ing_idx:
+                continue
+            egress_port = of_ports[egr_idx]
+            flow_match_test_port_pair(parent, ingress_port, egress_port, 
+                                      match=match, wildcards=wildcards,
+                                      dl_vlan=dl_vlan, mask=mask,
+                                      pkt=pkt, exp_pkt=exp_pkt,
+                                      apply_action_list=apply_action_list,
+                                      check_expire=check_expire)
+            test_count += 1
+            if (max_test > 0) and (test_count >= max_test):
+                logging.info("Ran " + str(test_count) + " tests; exiting")
+                return
+
+def flow_match_test_port_pair_vlan(parent, ing_port, egr_port, wildcards=0,
+                                   dl_vlan=ofp.OFPVID_NONE, dl_vlan_pcp=0,
+                                   dl_vlan_type=ETHERTYPE_VLAN,
+                                   dl_vlan_int=-1, dl_vlan_pcp_int=0,
+                                   vid_match=ofp.OFPVID_NONE, pcp_match=0,
+                                   exp_vid=-1, exp_pcp=0,
+                                   exp_vlan_type=ETHERTYPE_VLAN,
+                                   match_exp=True,
+                                   add_tag_exp=False,
+                                   exp_msg=ofp.OFPT_FLOW_REMOVED,
+                                   exp_msg_type=0, exp_msg_code=0,
+                                   pkt=None, exp_pkt=None,
+                                   action_list=None, check_expire=False):
+    """
+    Flow match test for various vlan matching patterns on single TCP packet
+
+    Run test with packet through switch from ing_port to egr_port
+    See flow_match_test_vlan for parameter descriptions
+    """
+    logging.info("Pkt match test: " + str(ing_port) + " to " + str(egr_port))
+    logging.debug("  WC: " + hex(wildcards) + " vlan: " + str(dl_vlan) +
+                    " expire: " + str(check_expire))
+    if pkt is None:
+        if dl_vlan >= 0 and dl_vlan != ofp.OFPVID_NONE:
+            if dl_vlan_int >= 0 and dl_vlan_int != ofp.OFPVID_NONE:
+                pkt = simple_tcp_packet(
+                        vlan_tags=[{'type': dl_vlan_type, 'vid': dl_vlan, 'pcp': dl_vlan_pcp},
+                                   {'vid': dl_vlan_int, 'pcp': dl_vlan_pcp_int}])
+            else:
+                pkt = simple_tcp_packet(
+                        vlan_tags=[{'type': dl_vlan_type, 'vid': dl_vlan, 'pcp': dl_vlan_pcp}])
+        else:
+            pkt = simple_tcp_packet()
+
+    if exp_pkt is None:
+        if exp_vid >= 0 and exp_vid != ofp.OFPVID_NONE:
+            if add_tag_exp:
+                if dl_vlan >= 0 and dl_vlan != ofp.OFPVID_NONE:
+                    if dl_vlan_int >= 0 and dl_vlan_int != ofp.OFPVID_NONE:
+                        exp_pkt = simple_tcp_packet(
+                                    vlan_tags=[{'type': exp_vlan_type, 'vid': exp_vid, 'pcp': exp_pcp},
+                                               {'type': dl_vlan_type, 'vid': dl_vlan, 'pcp': dl_vlan_pcp},
+                                               {'vid': dl_vlan_int, 'pcp': dl_vlan_pcp_int}])
+                    else:
+                        exp_pkt = simple_tcp_packet(
+                                    vlan_tags=[{'type': exp_vlan_type, 'vid': exp_vid, 'pcp': exp_pcp},
+                                               {'type': dl_vlan_type, 'vid': dl_vlan, 'pcp': dl_vlan_pcp}])
+                else:
+                    exp_pkt = simple_tcp_packet(
+                                vlan_tags=[{'type': exp_vlan_type, 'vid': exp_vid, 'pcp': exp_pcp}])
+            else:
+                if dl_vlan_int >= 0:
+                    exp_pkt = simple_tcp_packet(
+                                vlan_tags=[{'type': exp_vlan_type, 'vid': exp_vid, 'pcp': exp_pcp},
+                                           {'vid': dl_vlan_int, 'pcp': dl_vlan_pcp_int}])
+
+                else:
+                    exp_pkt = simple_tcp_packet(
+                                vlan_tags=[{'type': exp_vlan_type, 'vid': exp_vid, 'pcp': exp_pcp}])
+        else:
+            #subtract ofp.action
+            if dl_vlan_int >= 0:
+                exp_pkt = simple_tcp_packet(
+                            vlan_tags=[{'vid': dl_vlan_int, 'pcp': dl_vlan_pcp_int}])
+            else:
+                exp_pkt = simple_tcp_packet()
+
+    match = parse.packet_to_flow_match(pkt)
+    parent.assertTrue(match is not None, "Flow match from pkt failed")
+
+    match.dl_vlan = vid_match
+    match.dl_vlan_pcp = pcp_match
+    match.wildcards = wildcards
+
+    request = flow_msg_create(parent, pkt, ing_port=ing_port,
+                              wildcards=wildcards,
+                              match=match,
+                              egr_port=egr_port,
+                              action_list=action_list)
+
+    flow_msg_install(parent, request)
+
+    logging.debug("Send packet: " + str(ing_port) + " to " + str(egr_port))
+    logging.debug("Sent:" + str(pkt).encode('hex'))
+    parent.dataplane.send(ing_port, str(pkt))
+
+    if match_exp:
+        receive_pkt_verify(parent, egr_port, exp_pkt)
+        if check_expire:
+            #@todo Not all HW supports both pkt and byte counters
+            flow_removed_verify(parent, request, pkt_count=1, byte_count=len(pkt))
+    else:
+        if exp_msg is ofp.OFPT_FLOW_REMOVED:
+            if check_expire:
+                flow_removed_verify(parent, request, pkt_count=0, byte_count=0)
+        elif exp_msg is ofp.OFPT_ERROR:
+            error_verify(parent, exp_msg_type, exp_msg_code)
+        else:
+            parent.assertTrue(0, "Rcv: Unexpected ofp.message: " + str(exp_msg))
+
+        (_, rcv_pkt, _) = parent.dataplane.poll(timeout=1)
+        parent.assertFalse(rcv_pkt is not None, "Packet on dataplane")
+
+def flow_match_test_vlan(parent, port_map, wildcards=0,
+                         dl_vlan=ofp.OFPVID_NONE, dl_vlan_pcp=0, dl_vlan_type=ETHERTYPE_VLAN,
+                         dl_vlan_int=-1, dl_vlan_pcp_int=0,
+                         vid_match=ofp.OFPVID_NONE, pcp_match=0,
+                         exp_vid=-1, exp_pcp=0,
+                         exp_vlan_type=ETHERTYPE_VLAN,
+                         match_exp=True,
+                         add_tag_exp=False,
+                         exp_msg=ofp.OFPT_FLOW_REMOVED,
+                         exp_msg_type=0, exp_msg_code=0,
+                         pkt=None, exp_pkt=None,
+                         action_list=None,
+                         check_expire=False,
+                         max_test=0):
+    """
+    Run flow_match_test_port_pair on all port pairs
+
+    @param max_test If > 0 no more than this number of tests are executed.
+    @param parent Must implement controller, dataplane, assertTrue, assertEqual
+    and logger
+    @param wildcards For flow match entry
+    @param dl_vlan If not -1, and pkt is not None, create a pkt w/ VLAN tag
+    @param dl_vlan_pcp VLAN PCP associated with dl_vlan
+    @param dl_vlan_type VLAN ether type associated with dl_vlan
+    @param dl_vlan_int If not -1, create pkt w/ Inner Vlan tag
+    @param dl_vlan_pcp_int VLAN PCP associated with dl_vlan_2nd
+    @param vid_match Matching value for VLAN VID field
+    @param pcp_match Matching value for VLAN PCP field
+    @param exp_vid Expected VLAN VID value. If -1, no VLAN expected
+    @param exp_vlan_type Expected VLAN ether type
+    @param exp_pcp Expected VLAN PCP value
+    @param match_exp Set whether packet is expected to receive
+    @param add_tag_exp If True, expected_packet has an additional vlan tag,
+    If not, expected_packet's vlan tag is replaced as specified
+    @param exp_msg Expected ofp.message
+    @param exp_msg_type Expected ofp.message type associated with the ofp.message
+    @param exp_msg_code Expected ofp.message code associated with the msg_type
+    @param pkt If not None, use this packet for ingress
+    @param exp_pkt If not None, use this as the expected output pkt
+    @param action_list Additional actions to add to flow mod
+    @param check_expire Check for flow expiration ofp.message
+    """
+    of_ports = port_map.keys()
+    of_ports.sort()
+    parent.assertTrue(len(of_ports) > 1, "Not enough ports for test")
+    test_count = 0
+
+    for ing_idx in range(len(of_ports)):
+        ingress_port = of_ports[ing_idx]
+        for egr_idx in range(len(of_ports)):
+            if egr_idx == ing_idx:
+                continue
+            egress_port = of_ports[egr_idx]
+            flow_match_test_port_pair_vlan(parent, ingress_port, egress_port,
+                                           wildcards=wildcards,
+                                           dl_vlan=dl_vlan,
+                                           dl_vlan_pcp=dl_vlan_pcp,
+                                           dl_vlan_type=dl_vlan_type,
+                                           dl_vlan_int=dl_vlan_int,
+                                           dl_vlan_pcp_int=dl_vlan_pcp_int,
+                                           vid_match=vid_match,
+                                           pcp_match=pcp_match,
+                                           exp_vid=exp_vid,
+                                           exp_pcp=exp_pcp,
+                                           exp_vlan_type=exp_vlan_type,
+                                           exp_msg=exp_msg,
+                                           exp_msg_type=exp_msg_type,
+                                           exp_msg_code=exp_msg_code,
+                                           match_exp=match_exp,
+                                           add_tag_exp=add_tag_exp,
+                                           pkt=pkt, exp_pkt=exp_pkt,
+                                           action_list=action_list,
+                                           check_expire=check_expire)
+            test_count += 1
+            if (max_test > 0) and (test_count >= max_test):
+                logging.info("Ran " + str(test_count) + " tests; exiting")
+                return
+
+def test_param_get(config, key, default=None):
+    """
+    Return value passed via test-params if present
+
+    @param config The configuration structure for OFTest
+    @param key The lookup key
+    @param default Default value to use if not found
+
+    If the pair 'key=val' appeared in the string passed to --test-params
+    on the command line, return val (as interpreted by exec).  Otherwise
+    return default value.
+    """
+    try:
+        exec config["test_params"]
+    except:
+        return default
+
+    s = "val = " + str(key)
+    try:
+        val = None
+        exec s
+        return val
+    except:
+        return default
+
+def action_generate(parent, field_to_mod, mod_field_vals):
+    """
+    Create an ofp.action to modify the field indicated in field_to_mod
+
+    @param parent Must implement, assertTrue
+    @param field_to_mod The field to modify as a string name
+    @param mod_field_vals Hash of values to use for modified values
+    """
+
+    act = None
+
+    if field_to_mod in ['pktlen']:
+        return None
+
+    if field_to_mod == 'dl_dst':
+        act = ofp.action.set_dl_dst()
+        act.dl_addr = parse.parse_mac(mod_field_vals['dl_dst'])
+    elif field_to_mod == 'dl_src':
+        act = ofp.action.set_dl_src()
+        act.dl_addr = parse.parse_mac(mod_field_vals['dl_src'])
+    elif field_to_mod == 'vlan_tags':
+        if len(mod_field_vals['vlan_tags']):
+            act = ofp.action.pop_vlan()
+        else:
+            pass
+#    elif field_to_mod == 'dl_vlan_enable':
+#        if not mod_field_vals['dl_vlan_enable']: # Strip VLAN tag
+#            act = ofp.action.pop_vlan()
+#        # Add VLAN tag is handled by dl_vlan field
+#        # Will return None in this case
+#    elif field_to_mod == 'dl_vlan':
+#        act = ofp.action.set_vlan_vid()
+#        act.vlan_vid = mod_field_vals['dl_vlan']
+#    elif field_to_mod == 'dl_vlan_pcp':
+#        act = ofp.action.set_vlan_pcp()
+#        act.vlan_pcp = mod_field_vals['dl_vlan_pcp']
+    elif field_to_mod == 'ip_src':
+        act = ofp.action.set_nw_src()
+        act.nw_addr = parse.parse_ip(mod_field_vals['ip_src'])
+    elif field_to_mod == 'ip_dst':
+        act = ofp.action.set_nw_dst()
+        act.nw_addr = parse.parse_ip(mod_field_vals['ip_dst'])
+    elif field_to_mod == 'ip_tos':
+        act = ofp.action.set_nw_tos()
+        act.nw_tos = mod_field_vals['ip_tos']
+    elif field_to_mod == 'tcp_sport':
+        act = ofp.action.set_tp_src()
+        act.tp_port = mod_field_vals['tcp_sport']
+    elif field_to_mod == 'tcp_dport':
+        act = ofp.action.set_tp_dst()
+        act.tp_port = mod_field_vals['tcp_dport']
+    else:
+        parent.assertTrue(0, "Unknown field to modify: " + str(field_to_mod))
+
+    return act
+
+def pkt_action_setup(parent, start_field_vals={}, mod_field_vals={}, 
+                     mod_fields={}, check_test_params=False):
+    """
+    Set up the ingress and expected packet and ofp.action list for a test
+
+    @param parent Must implement, assertTrue, config hash and logger
+    @param start_field_values Field values to use for ingress packet (optional)
+    @param mod_field_values Field values to use for modified packet (optional)
+    @param mod_fields The list of fields to be modified by the switch in the test.
+    @params check_test_params If True, will check the parameters vid, add_vlan
+    and strip_vlan from the command line.
+
+    Returns a triple:  pkt-to-send, expected-pkt, ofp.action-list
+    """
+
+    new_actions = []
+
+
+    base_pkt_params = {}
+    base_pkt_params['dl_dst'] = '00:DE:F0:12:34:56'
+    base_pkt_params['dl_src'] = '00:23:45:67:89:AB'
+#    base_pkt_params['dl_vlan_enable'] = False
+#    base_pkt_params['dl_vlan'] = 2
+#    base_pkt_params['dl_vlan_pcp'] = 0
+    base_pkt_params['ip_src'] = '192.168.0.1'
+    base_pkt_params['ip_dst'] = '192.168.0.2'
+    base_pkt_params['ip_tos'] = 0
+    base_pkt_params['tcp_sport'] = 1234
+    base_pkt_params['tcp_dport'] = 80
+    for keyname in start_field_vals.keys():
+        base_pkt_params[keyname] = start_field_vals[keyname]
+
+    mod_pkt_params = {}
+    mod_pkt_params['dl_dst'] = '00:21:0F:ED:CB:A9'
+    mod_pkt_params['dl_src'] = '00:ED:CB:A9:87:65'
+#    mod_pkt_params['dl_vlan_enable'] = False
+#    mod_pkt_params['dl_vlan'] = 3
+#    mod_pkt_params['dl_vlan_pcp'] = 7
+    mod_pkt_params['ip_src'] = '10.20.30.40'
+    mod_pkt_params['ip_dst'] = '50.60.70.80'
+    mod_pkt_params['ip_tos'] = 0xf0
+    mod_pkt_params['tcp_sport'] = 4321
+    mod_pkt_params['tcp_dport'] = 8765
+    for keyname in mod_field_vals.keys():
+        mod_pkt_params[keyname] = mod_field_vals[keyname]
+
+    # Check for test param modifications
+    strip = False
+    if check_test_params:
+        add_vlan = test_param_get(parent.config, 'add_vlan')
+        strip_vlan = test_param_get(parent.config, 'strip_vlan')
+        vid = test_param_get(parent.config, 'vid')
+
+        if add_vlan and strip_vlan:
+            parent.assertTrue(0, "Add and strip VLAN both specified")
+
+        if vid:
+            base_pkt_params['dl_vlan_enable'] = True
+            base_pkt_params['dl_vlan'] = vid
+            if 'dl_vlan' in mod_fields:
+                mod_pkt_params['dl_vlan'] = vid + 1
+
+        if add_vlan:
+            base_pkt_params['dl_vlan_enable'] = False
+            mod_pkt_params['dl_vlan_enable'] = True
+            mod_pkt_params['pktlen'] = base_pkt_params['pktlen'] + 4
+            mod_fields.append('pktlen')
+            mod_fields.append('dl_vlan_enable')
+            if 'dl_vlan' not in mod_fields:
+                mod_fields.append('dl_vlan')
+        elif strip_vlan:
+            base_pkt_params['dl_vlan_enable'] = True
+            mod_pkt_params['dl_vlan_enable'] = False
+            mod_pkt_params['pktlen'] = base_pkt_params['pktlen'] - 4
+            mod_fields.append('dl_vlan_enable')
+            mod_fields.append('pktlen')
+
+    # Build the ingress packet
+    ingress_pkt = simple_tcp_packet(**base_pkt_params)
+
+    # Build the expected packet, modifying the indicated fields
+    for item in mod_fields:
+        base_pkt_params[item] = mod_pkt_params[item]
+        act = action_generate(parent, item, mod_pkt_params)
+        if act:
+            new_actions.append(act)
+
+    expected_pkt = simple_tcp_packet(**base_pkt_params)
+
+    return (ingress_pkt, expected_pkt, new_actions)
+        
+def wildcard_all_set(match):
+    match.wildcards = ofp.OFPFW_ALL
+    match.nw_dst_mask = 0xffffffff
+    match.nw_src_mask = 0xffffffff
+    match.dl_dst_mask = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff]
+    match.dl_src_mask = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff]
+    match.metadata_mask = 0xffffffffffffffff
+
+def skip_message_emit(parent, s):
+    """
+    Print out a 'skipped' ofp.message to stderr
+
+    @param s The string to print out to the log file
+    @param parent Must implement config and logger objects
+    """
+    global skipped_test_count
+
+    skipped_test_count += 1
+    logging.info("Skipping: " + s)
+    if parent.config["debug"] < logging.WARNING:
+        sys.stderr.write("(skipped) ")
+    else:
+        sys.stderr.write("(S)")
+
+def do_echo_request_reply_test(test,controller):
+        request = ofp.message.echo_request()
+        response, _ = controller.transact(request)
+        test.assertEqual(response.type, ofp.OFPT_ECHO_REPLY,
+                         'response is not echo_reply')
+        test.assertEqual(request.xid, response.xid,
+                         'response xid != request xid')
+        test.assertEqual(len(response.data), 0, 'response data non-empty')
+
+def match_all_generate():
+    match = ofp.ofp_match()
+    return match
+
+def simple_tcp_packet_w_mpls(
+                      dl_dst='00:01:02:03:04:05',
+                      dl_src='00:06:07:08:09:0a',
+                      mpls_type=0x8847,
+                      mpls_label=-1,
+                      mpls_tc=0,
+                      mpls_ttl=64,
+                      mpls_label_int=-1,
+                      mpls_tc_int=0,
+                      mpls_ttl_int=32,
+                      mpls_label_ext=-1,
+                      mpls_tc_ext=0,
+                      mpls_ttl_ext=128,
+                      ip_src='192.168.0.1',
+                      ip_dst='192.168.0.2',
+                      ip_tos=0,
+                      ip_ttl=192,
+                      tcp_sport=1234,
+                      tcp_dport=80
+                      ):
+    """
+    Return a simple dataplane TCP packet w/wo MPLS tags
+
+    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 mpls_type MPLS type as ether type
+    @param mpls_label MPLS LABEL if not -1
+    @param mpls_tc MPLS TC
+    @param mpls_ttl MPLS TTL
+    @param mpls_label_int Inner MPLS LABEL if not -1. The shim will be added
+    inside of mpls_label shim.
+    @param mpls_tc_int Inner MPLS TC
+    @param mpls_ttl_int Inner MPLS TTL
+    @param mpls_label_ext External MPLS LABEL if not -1. The shim will be
+    added outside of mpls_label shim
+    @param mpls_tc_ext External MPLS TC
+    @param mpls_ttl_ext External MPLS TTL
+    @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 MPLS/IP/TCP request.  Users
+    shouldn't assume anything about this packet other than that
+    it is a valid ethernet/IP/TCP frame.
+    """
+    
+    mpls_tags = []
+    
+    if mpls_label_ext >= 0:
+        mpls_tags.append({'type': mpls_type, 'label': mpls_label_ext, 'tc': mpls_tc_ext, 'ttl': mpls_ttl_ext})
+        
+    if mpls_label >= 0:
+        mpls_tags.append({'type': mpls_type, 'label': mpls_label, 'tc': mpls_tc, 'ttl': mpls_ttl})
+        
+    if mpls_label_int >= 0:
+        mpls_tags.append({'type': mpls_type, 'label': mpls_label_int, 'tc': mpls_tc_int, 'ttl': mpls_ttl_int})
+    
+    pkt = simple_tcp_packet(dl_dst=dl_dst,
+                            dl_src=dl_src,
+                            mpls_tags=mpls_tags,
+                            ip_src=ip_src,
+                            ip_dst=ip_dst,
+                            ip_tos=ip_tos,  
+                            ip_ttl=ip_ttl,
+                            tcp_sport=tcp_sport,
+                            tcp_dport=tcp_dport)
+    return pkt
+    
+def flow_match_test_port_pair_mpls(parent, ing_port, egr_port, wildcards=0,
+                                   mpls_type=0x8847,
+                                   mpls_label=-1, mpls_tc=0,mpls_ttl=64,
+                                   mpls_label_int=-1, mpls_tc_int=0,
+                                   mpls_ttl_int=32,
+                                   ip_ttl=192,
+                                   exp_mpls_type=0x8847,
+                                   exp_mpls_label=-1, exp_mpls_tc=0,
+                                   exp_mpls_ttl=64,
+                                   exp_mpls_ttl_int=32,
+                                   exp_ip_ttl=192,
+                                   label_match=0, tc_match=0,
+                                   dl_type_match=ETHERTYPE_MPLS,
+                                   match_exp=True,
+                                   add_tag_exp=False,
+                                   exp_msg=ofp.OFPT_FLOW_REMOVED,
+                                   exp_msg_type=0, exp_msg_code=0,
+                                   pkt=None,
+                                   exp_pkt=None, action_list=None,
+                                   check_expire=False):
+    """
+    Flow match test on single packet w/ MPLS tags
+
+    Run test with packet through switch from ing_port to egr_port
+    See flow_match_test for parameter descriptions
+    """
+    logging.info("Pkt match test: " + str(ing_port) + " to " + str(egr_port))
+    logging.debug("  WC: " + hex(wildcards) + " MPLS: " +
+                    str(mpls_label) + " expire: " + str(check_expire))
+
+    if pkt is None:
+
+        pkt = simple_tcp_packet_w_mpls(mpls_type=mpls_type,
+                                       mpls_label=mpls_label,
+                                       mpls_tc=mpls_tc,
+                                       mpls_ttl=mpls_ttl,
+                                       mpls_label_int=mpls_label_int,
+                                       mpls_tc_int=mpls_tc_int,
+                                       mpls_ttl_int=mpls_ttl_int,
+                                       ip_ttl=ip_ttl)
+
+    if exp_pkt is None:
+        if add_tag_exp:
+            exp_pkt = simple_tcp_packet_w_mpls(
+                                           mpls_type=exp_mpls_type,
+                                           mpls_label_ext=exp_mpls_label,
+                                           mpls_tc_ext=exp_mpls_tc,
+                                           mpls_ttl_ext=exp_mpls_ttl,
+                                           mpls_label=mpls_label,
+                                           mpls_tc=mpls_tc,
+                                           mpls_ttl=mpls_ttl,
+                                           mpls_label_int=mpls_label_int,
+                                           mpls_tc_int=mpls_tc_int,
+                                           mpls_ttl_int=exp_mpls_ttl_int,
+                                           ip_ttl=exp_ip_ttl)
+        else:
+            if (exp_mpls_label < 0) and (mpls_label_int >= 0):
+                exp_pkt = simple_tcp_packet_w_mpls(
+                                           mpls_type=mpls_type,
+                                           mpls_label=mpls_label_int,
+                                           mpls_tc=mpls_tc_int,
+                                           mpls_ttl=exp_mpls_ttl_int,
+                                           ip_ttl=exp_ip_ttl)
+            else:
+                exp_pkt = simple_tcp_packet_w_mpls(
+                                           mpls_type=exp_mpls_type,
+                                           mpls_label=exp_mpls_label,
+                                           mpls_tc=exp_mpls_tc,
+                                           mpls_ttl=exp_mpls_ttl,
+                                           mpls_label_int=mpls_label_int,
+                                           mpls_tc_int=mpls_tc_int,
+                                           mpls_ttl_int=exp_mpls_ttl_int,
+                                           ip_ttl=exp_ip_ttl)
+    wildcards = (ofp.OFPFW_ALL & ~(ofp.OFPFW_DL_TYPE | ofp.OFPFW_MPLS_LABEL | ofp.OFPFW_MPLS_TC)) | wildcards
+
+    match = parse.packet_to_flow_match(pkt)
+    parent.assertTrue(match is not None, "Flow match from pkt failed")
+
+    match.mpls_label = label_match
+    match.mpls_tc = tc_match
+    match.wildcards = wildcards
+
+    match.dl_type = dl_type_match
+    match.nw_tos = 0
+    match.nw_proto = 0
+    match.nw_src = 0
+    match.nw_src_mask = 0xFFFFFFFF
+    match.nw_dst = 0
+    match.nw_dst_mask = 0xFFFFFFFF
+    match.tp_src = 0
+    match.tp_dst = 0
+
+    request = flow_msg_create(parent, pkt, ing_port=ing_port,
+                              wildcards=wildcards,
+                              match=match,
+                              egr_port=egr_port,
+                              action_list=action_list)
+
+    flow_msg_install(parent, request)
+
+    logging.debug("Send packet: " + str(ing_port) + " to " + str(egr_port))
+    #logging.debug(str(pkt).encode("hex"))
+    parent.dataplane.send(ing_port, str(pkt))
+
+    if match_exp:
+        receive_pkt_verify(parent, egr_port, exp_pkt)
+        if check_expire:
+            #@todo Not all HW supports both pkt and byte counters
+            flow_removed_verify(parent, request, pkt_count=1, byte_count=len(pkt))
+    else:
+        if exp_msg == ofp.OFPT_FLOW_REMOVED:
+            if check_expire:
+                flow_removed_verify(parent, request, pkt_count=0, byte_count=0)
+        elif exp_msg == ofp.OFPT_ERROR:
+            error_verify(parent, exp_msg_type, exp_msg_code)
+        else:
+            parent.assertTrue(0, "Rcv: Unexpected ofp.message: " + str(exp_msg))
+        (_, rcv_pkt, _) = parent.dataplane.poll(timeout=1)
+        parent.assertFalse(rcv_pkt is not None, "Packet on dataplane")
+
+def flow_match_test_mpls(parent, port_map, wildcards=0,
+                         mpls_type=0x8847,
+                         mpls_label=-1, mpls_tc=0, mpls_ttl=64,
+                         mpls_label_int=-1, mpls_tc_int=0, mpls_ttl_int=32,
+                         ip_ttl = 192,
+                         label_match=0, tc_match=0,
+                         dl_type_match=ETHERTYPE_MPLS,
+                         exp_mpls_type=0x8847,
+                         exp_mpls_label=-1, exp_mpls_tc=0, exp_mpls_ttl=64,
+                         exp_mpls_ttl_int=32,
+                         exp_ip_ttl=192,
+                         match_exp=True,
+                         add_tag_exp=False,
+                         exp_msg=ofp.OFPT_FLOW_REMOVED,
+                         exp_msg_type=0, exp_msg_code=0,
+                         pkt=None,
+                         exp_pkt=None, action_list=None, check_expire=False,
+                         max_test=0):
+    """
+    Run flow_match_test_port_pair on all port pairs
+
+    @param max_test If > 0 no more than this number of tests are executed.
+    @param parent Must implement controller, dataplane, assertTrue, assertEqual
+    and logger
+    @param wildcards For flow match entry
+    @param mpls_type MPLS type
+    @param mpls_label If not -1 create a pkt w/ MPLS tag
+    @param mpls_tc MPLS TC associated with MPLS label
+    @param mpls_ttl MPLS TTL associated with MPLS label
+    @param mpls_label_int If not -1 create a pkt w/ Inner MPLS tag
+    @param mpls_tc_int MPLS TC associated with Inner MPLS label
+    @param mpls_ttl_int MPLS TTL associated with Inner MPLS label
+    @param ip_ttl IP TTL
+    @param label_match Matching value for MPLS LABEL field
+    @param tc_match Matching value for MPLS TC field
+    @param exp_mpls_label Expected MPLS LABEL value. If -1, no MPLS expected
+    @param exp_mpls_tc Expected MPLS TC value
+    @param exp_mpls_ttl Expected MPLS TTL value
+    @param exp_mpls_ttl_int Expected Inner MPLS TTL value
+    @param ip_ttl Expected IP TTL
+    @param match_exp Set whether packet is expected to receive
+    @param add_tag_exp If True, expected_packet has an additional MPLS shim,
+    If not expected_packet's MPLS shim is replaced as specified
+    @param exp_msg Expected ofp.message
+    @param exp_msg_type Expected ofp.message type associated with the ofp.message
+    @param exp_msg_code Expected ofp.message code associated with the msg_type
+    @param pkt If not None, use this packet for ingress
+    @param exp_pkt If not None, use this as the expected output pkt; els use pkt
+    @param action_list Additional actions to add to flow mod
+    @param check_expire Check for flow expiration ofp.message
+    """
+    of_ports = port_map.keys()
+    of_ports.sort()
+    parent.assertTrue(len(of_ports) > 1, "Not enough ports for test")
+    test_count = 0
+
+    for ing_idx in range(len(of_ports)):
+        ingress_port = of_ports[ing_idx]
+        for egr_idx in range(len(of_ports)):
+            if egr_idx == ing_idx:
+                continue
+            egress_port = of_ports[egr_idx]
+            flow_match_test_port_pair_mpls(parent, ingress_port, egress_port,
+                                      wildcards=wildcards,
+                                      mpls_type=mpls_type,
+                                      mpls_label=mpls_label,
+                                      mpls_tc=mpls_tc,
+                                      mpls_ttl=mpls_ttl,
+                                      mpls_label_int=mpls_label_int,
+                                      mpls_tc_int=mpls_tc_int,
+                                      mpls_ttl_int=mpls_ttl_int,
+                                      ip_ttl=ip_ttl,
+                                      label_match=label_match,
+                                      tc_match=tc_match,
+                                      dl_type_match=dl_type_match,
+                                      exp_mpls_type=exp_mpls_type,
+                                      exp_mpls_label=exp_mpls_label,
+                                      exp_mpls_tc=exp_mpls_tc,
+                                      exp_mpls_ttl=exp_mpls_ttl,
+                                      exp_mpls_ttl_int=exp_mpls_ttl_int,
+                                      exp_ip_ttl=exp_ip_ttl,
+                                      match_exp=match_exp,
+                                      exp_msg=exp_msg,
+                                      exp_msg_type=exp_msg_type,
+                                      exp_msg_code=exp_msg_code,
+                                      add_tag_exp=add_tag_exp,
+                                      pkt=pkt, exp_pkt=exp_pkt,
+                                      action_list=action_list,
+                                      check_expire=check_expire)
+            test_count += 1
+            if (max_test > 0) and (test_count >= max_test):
+                logging.info("Ran " + str(test_count) + " tests; exiting")
+                return
+
+def flow_stats_get(parent, match_fields = None):
+    """ Get the flow_stats from the switch
+    Test the response to make sure it's really a flow_stats object
+    """
+    request = ofp.message.flow_stats_request()
+    request.out_port = ofp.OFPP_ANY
+    request.out_group = ofp.OFPG_ANY
+    request.table_id = 0xff
+    if match_fields != None:
+        request.match_fields = match_fields
+    response, _ = parent.controller.transact(request, timeout=2)
+    parent.assertTrue(response is not None, "Did not get response")
+    parent.assertTrue(isinstance(response,ofp.message.flow_stats_reply),
+                      "Expected a flow_stats_reply, but didn't get it")
+    return response
diff --git a/Fabric/Utilities/src/python/oftest/ofutils.py b/Fabric/Utilities/src/python/oftest/ofutils.py
new file mode 100644
index 0000000..7e6044d
--- /dev/null
+++ b/Fabric/Utilities/src/python/oftest/ofutils.py
@@ -0,0 +1,80 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
+
+"""
+Utilities for the OpenFlow test framework
+"""
+
+import random
+import time
+import os
+import fcntl
+import logging
+
+default_timeout = None # set by oft
+default_negative_timeout = None # set by oft
+
+def gen_xid():
+    return random.randrange(1,0xffffffff)
+
+"""
+Wait on a condition variable until the given function returns non-None or a timeout expires.
+The condition variable must already be acquired.
+The timeout value -1 means use the default timeout.
+There is deliberately no support for an infinite timeout.
+"""
+def timed_wait(cv, fn, timeout=-1):
+    if timeout == -1:
+        timeout = default_timeout
+
+    end_time = time.time() + timeout
+    while True:
+        val = fn()
+        if val != None:
+            return val
+
+        remaining_time = end_time - time.time()
+        cv.wait(remaining_time)
+
+        if time.time() > end_time:
+            return None
+
+class EventDescriptor():
+    """
+    Similar to a condition variable, but can be passed to select().
+    Only supports one waiter.
+    """
+
+    def __init__(self):
+        self.pipe_rd, self.pipe_wr = os.pipe()
+        fcntl.fcntl(self.pipe_wr, fcntl.F_SETFL, os.O_NONBLOCK)
+
+    def __del__(self):
+        os.close(self.pipe_rd)
+        os.close(self.pipe_wr)
+
+    def notify(self):
+        try:
+            os.write(self.pipe_wr, "x")
+        except OSError as e:
+            logging.warn("Failed to notify EventDescriptor: %s", e)
+
+    def wait(self):
+        os.read(self.pipe_rd, 1)
+
+    def fileno(self):
+        return self.pipe_rd
diff --git a/Fabric/Utilities/src/python/oftest/packet.py b/Fabric/Utilities/src/python/oftest/packet.py
new file mode 100755
index 0000000..fa887bc
--- /dev/null
+++ b/Fabric/Utilities/src/python/oftest/packet.py
@@ -0,0 +1,82 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
+# Distributed under the OpenFlow Software License (see LICENSE)
+# Copyright (c) 2010 The Board of Trustees of The Leland Stanford Junior University
+# Copyright (c) 2012, 2013 Big Switch Networks, Inc.
+"""
+Wrap scapy to satisfy pylint
+"""
+from oftest import config
+import sys
+
+try:
+    import scapy.config
+    import scapy.route
+    import scapy.layers.l2
+    import scapy.layers.inet
+    if not config["disable_ipv6"]:
+        import scapy.route6
+        import scapy.layers.inet6
+except ImportError:
+    sys.exit("Need to install scapy for packet parsing")
+
+Ether = scapy.layers.l2.Ether
+LLC = scapy.layers.l2.LLC
+SNAP = scapy.layers.l2.SNAP
+Dot1Q = scapy.layers.l2.Dot1Q
+IP = scapy.layers.inet.IP
+IPOption = scapy.layers.inet.IPOption
+ARP = scapy.layers.inet.ARP
+TCP = scapy.layers.inet.TCP
+UDP = scapy.layers.inet.UDP
+ICMP = scapy.layers.inet.ICMP
+
+
+from scapy.fields import *
+from scapy.packet import *
+
+class ThreeBytesField(X3BytesField, ByteField):
+    def i2repr(self, pkt, x):
+        return ByteField.i2repr(self, pkt, x)
+
+class VXLAN(Packet):
+    name = "VXLAN"
+    fields_desc = [ FlagsField("flags", 0x08, 8, ['R', 'R', 'R', 'I', 'R', 'R', 'R', 'R']),
+                    X3BytesField("reserved1", 0x000000),
+                    ThreeBytesField("vni", 0),
+                    XByteField("reserved2", 0x00)]
+
+    def mysummary(self):
+        return self.sprintf("VXLAN (vni=%VXLAN.vni%)")
+
+bind_layers(UDP, VXLAN, dport=4789)
+bind_layers(VXLAN, Ether)
+
+
+class MPLS(Packet): 
+   name = "MPLS" 
+   fields_desc =  [ BitField("label", 3, 20), 
+                    BitField("cos", 0, 3), 
+                    BitField("s", 1, 1), 
+                    ByteField("ttl", 0)  ] 
+
+bind_layers(Ether, MPLS, type=0x8847)
+
+if not config["disable_ipv6"]:
+    IPv6 = scapy.layers.inet6.IPv6
+    ICMPv6Unknown = scapy.layers.inet6.ICMPv6Unknown
+    ICMPv6EchoRequest = scapy.layers.inet6.ICMPv6EchoRequest
diff --git a/Fabric/Utilities/src/python/oftest/parse.py b/Fabric/Utilities/src/python/oftest/parse.py
new file mode 100644
index 0000000..1f94c45
--- /dev/null
+++ b/Fabric/Utilities/src/python/oftest/parse.py
@@ -0,0 +1,301 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
+"""
+Utility parsing functions
+"""
+
+import sys
+import socket
+import packet as scapy
+
+def parse_mac(mac_str):
+    """
+    Parse a MAC address
+
+    Parse a MAC address ':' separated string of hex digits to an
+    array of integer values.  '00:d0:05:5d:24:00' => [0, 208, 5, 93, 36, 0]
+    @param mac_str The string to convert
+    @return Array of 6 integer values
+    """
+    return map(lambda val: int(val, 16), mac_str.split(":"))
+
+def parse_ip(ip_str):
+    """
+    Parse an IP address
+
+    Parse an IP address '.' separated string of decimal digits to an
+    host ordered integer.  '172.24.74.77' => 
+    @param ip_str The string to convert
+    @return Integer value
+    """
+    array = map(lambda val: int(val), ip_str.split("."))
+    val = 0
+    for a in array:
+        val <<= 8
+        val += a
+    return val
+
+def parse_ipv6(ip_str):
+    """
+    Parse an IPv6 address
+
+    Parse a textual IPv6 address and return a 16 byte binary string.
+    """
+    return socket.inet_pton(socket.AF_INET6, ip_str)
+
+def packet_type_classify(ether):
+    try:
+        dot1q = ether[scapy.Dot1Q]
+    except:
+        dot1q = None
+
+    try:
+        ip = ether[scapy.IP]
+    except:
+        ip = None
+
+    try:
+        tcp = ether[scapy.TCP]
+    except:
+        tcp = None
+
+    try:
+        udp = ether[scapy.UDP]
+    except:
+        udp = None
+
+    try:
+        icmp = ether[scapy.ICMP]
+    except:
+        icmp = None
+
+    try:
+        arp = ether[scapy.ARP]
+    except:
+        arp = None
+    return (dot1q, ip, tcp, udp, icmp, arp)
+
+def packet_to_flow_match(packet):
+    """
+    Create a flow match that matches packet with the given wildcards
+
+    @param packet The packet to use as a flow template
+    @return An loxi.of10.match object
+
+    @todo check min length of packet
+    """
+    import ofp
+    if ofp.OFP_VERSION == 1:
+        return packet_to_flow_match_v1(packet)
+    elif ofp.OFP_VERSION == 3:
+        return packet_to_flow_match_v3(packet)
+    elif ofp.OFP_VERSION == 4:
+        return packet_to_flow_match_v4(packet)
+    elif ofp.OFP_VERSION == 5:
+        return packet_to_flow_match_v5(packet)
+    else:
+        raise NotImplementedError()
+
+def packet_to_flow_match_v1(packet):
+    """
+    OpenFlow 1.0 implementation of packet_to_flow_match
+    """
+    import loxi.of10 as ofp
+
+    if type(packet) == type(""):
+        ether = scapy.Ether(packet)
+    else:
+        ether = packet
+
+    # For now, assume ether IP packet and ignore wildcards
+    try:
+        (dot1q, ip, tcp, udp, icmp, arp) = packet_type_classify(ether)
+    except:
+        raise ValueError("could not classify packet")
+
+    match = ofp.match()
+    match.wildcards = ofp.OFPFW_ALL
+    #@todo Check if packet is other than L2 format
+    match.eth_dst = parse_mac(ether.dst)
+    match.wildcards &= ~ofp.OFPFW_DL_DST
+    match.eth_src = parse_mac(ether.src)
+    match.wildcards &= ~ofp.OFPFW_DL_SRC
+    match.eth_type = ether.type
+    match.wildcards &= ~ofp.OFPFW_DL_TYPE
+
+    if dot1q:
+        match.vlan_vid = dot1q.vlan
+        match.vlan_pcp = dot1q.prio
+        match.eth_type = dot1q.type
+    else:
+        match.vlan_vid = ofp.OFP_VLAN_NONE
+        match.vlan_pcp = 0
+    match.wildcards &= ~ofp.OFPFW_DL_VLAN
+    match.wildcards &= ~ofp.OFPFW_DL_VLAN_PCP
+
+    if ip:
+        match.ipv4_src = parse_ip(ip.src)
+        match.wildcards &= ~ofp.OFPFW_NW_SRC_MASK
+        match.ipv4_dst = parse_ip(ip.dst)
+        match.wildcards &= ~ofp.OFPFW_NW_DST_MASK
+        match.ip_dscp = ip.tos
+        match.wildcards &= ~ofp.OFPFW_NW_TOS
+
+    if tcp:
+        match.ip_proto = 6
+        match.wildcards &= ~ofp.OFPFW_NW_PROTO
+    elif not tcp and udp:
+        tcp = udp
+        match.ip_proto = 17
+        match.wildcards &= ~ofp.OFPFW_NW_PROTO
+
+    if tcp:
+        match.tcp_src = tcp.sport
+        match.wildcards &= ~ofp.OFPFW_TP_SRC
+        match.tcp_dst = tcp.dport
+        match.wildcards &= ~ofp.OFPFW_TP_DST
+
+    if icmp:
+        match.ip_proto = 1
+        match.tcp_src = icmp.type
+        match.tcp_dst = icmp.code
+        match.wildcards &= ~ofp.OFPFW_NW_PROTO
+
+    if arp:
+        match.ip_proto = arp.op
+        match.wildcards &= ~ofp.OFPFW_NW_PROTO
+        match.ipv4_src = parse_ip(arp.psrc)
+        match.wildcards &= ~ofp.OFPFW_NW_SRC_MASK
+        match.ipv4_dst = parse_ip(arp.pdst)
+        match.wildcards &= ~ofp.OFPFW_NW_DST_MASK
+
+    return match
+
+def packet_to_flow_match_v3(packet):
+    """
+    OpenFlow 1.2 implementation of packet_to_flow_match
+    """
+    import loxi.of12 as ofp
+    return packet_to_flow_match_oxm(packet, ofp)
+
+def packet_to_flow_match_v4(packet):
+    """
+    OpenFlow 1.3 implementation of packet_to_flow_match
+    """
+    import loxi.of13 as ofp
+    return packet_to_flow_match_oxm(packet, ofp)
+
+def packet_to_flow_match_v5(packet):
+    """
+    OpenFlow 1.3 implementation of packet_to_flow_match
+    """
+    import loxi.of14 as ofp
+    return packet_to_flow_match_oxm(packet, ofp)
+
+def packet_to_flow_match_oxm(packet, ofp):
+    def parse_ether_layer(layer, match):
+        assert(type(layer) == scapy.Ether)
+        match.oxm_list.append(ofp.oxm.eth_dst(parse_mac(layer.dst)))
+        match.oxm_list.append(ofp.oxm.eth_src(parse_mac(layer.src)))
+
+        if type(layer.payload) == scapy.Dot1Q:
+            layer = layer.payload
+            match.oxm_list.append(ofp.oxm.eth_type(layer.type))
+            match.oxm_list.append(ofp.oxm.vlan_vid(ofp.OFPVID_PRESENT|layer.vlan))
+            match.oxm_list.append(ofp.oxm.vlan_pcp(layer.prio))
+        else:
+            match.oxm_list.append(ofp.oxm.eth_type(layer.type))
+            match.oxm_list.append(ofp.oxm.vlan_vid(ofp.OFP_VLAN_NONE))
+
+        if type(layer.payload) == scapy.IP:
+            parse_ipv4_layer(layer.payload, match)
+        elif type(layer.payload) == scapy.IPv6:
+            parse_ipv6_layer(layer.payload, match)
+        elif type(layer.payload) == scapy.ARP:
+            parse_arp_layer(layer.payload, match)
+        # TODO MPLS
+
+    def parse_ipv4_layer(layer, match):
+        assert(type(layer) == scapy.IP)
+        match.oxm_list.append(ofp.oxm.ip_proto(layer.proto))
+        match.oxm_list.append(ofp.oxm.ip_dscp(layer.tos >> 2))
+        match.oxm_list.append(ofp.oxm.ip_ecn(layer.tos & 3))
+        match.oxm_list.append(ofp.oxm.ipv4_src(parse_ip(layer.src)))
+        match.oxm_list.append(ofp.oxm.ipv4_dst(parse_ip(layer.dst)))
+
+        if type(layer.payload) == scapy.TCP:
+            parse_tcp_layer(layer.payload, match)
+        elif type(layer.payload) == scapy.UDP:
+            parse_udp_layer(layer.payload, match)
+        elif type(layer.payload) == scapy.ICMP:
+            parse_icmpv4_layer(layer.payload, match)
+        # TODO SCTP
+
+    def parse_tcp_layer(layer, match):
+        assert(type(layer) == scapy.TCP)
+        match.oxm_list.append(ofp.oxm.tcp_src(layer.sport))
+        match.oxm_list.append(ofp.oxm.tcp_dst(layer.dport))
+
+    def parse_udp_layer(layer, match):
+        assert(type(layer) == scapy.UDP)
+        match.oxm_list.append(ofp.oxm.udp_src(layer.sport))
+        match.oxm_list.append(ofp.oxm.udp_dst(layer.dport))
+
+    def parse_icmpv4_layer(layer, match):
+        assert(type(layer) == scapy.ICMP)
+        match.oxm_list.append(ofp.oxm.icmpv4_type(layer.type))
+        match.oxm_list.append(ofp.oxm.icmpv4_code(layer.code))
+
+    def parse_arp_layer(layer, match):
+        assert(type(layer) == scapy.ARP)
+        match.oxm_list.append(ofp.oxm.arp_op(layer.op))
+        match.oxm_list.append(ofp.oxm.arp_spa(parse_ip(layer.psrc)))
+        match.oxm_list.append(ofp.oxm.arp_tpa(parse_ip(layer.pdst)))
+        match.oxm_list.append(ofp.oxm.arp_sha(parse_mac(layer.hwsrc)))
+        match.oxm_list.append(ofp.oxm.arp_tha(parse_mac(layer.hwdst)))
+
+    def parse_ipv6_layer(layer, match):
+        assert(type(layer) == scapy.IPv6)
+        # TODO handle chained headers
+        match.oxm_list.append(ofp.oxm.ip_proto(layer.nh))
+        match.oxm_list.append(ofp.oxm.ip_dscp(layer.tc >> 2))
+        match.oxm_list.append(ofp.oxm.ip_ecn(layer.tc & 3))
+        match.oxm_list.append(ofp.oxm.ipv6_src(parse_ipv6(layer.src)))
+        match.oxm_list.append(ofp.oxm.ipv6_dst(parse_ipv6(layer.dst)))
+        match.oxm_list.append(ofp.oxm.ipv6_flabel(layer.fl))
+
+        if type(layer.payload) == scapy.TCP:
+            parse_tcp_layer(layer.payload, match)
+        elif type(layer.payload) == scapy.UDP:
+            parse_udp_layer(layer.payload, match)
+        elif layer.nh == 0x3a:
+            parse_icmpv6_layer(layer.payload, match)
+        # TODO ND
+        # TODO SCTP
+
+    def parse_icmpv6_layer(layer, match):
+        match.oxm_list.append(ofp.oxm.icmpv6_type(layer.type))
+        match.oxm_list.append(ofp.oxm.icmpv6_code(layer.code))
+
+    if type(packet) == type(""):
+        ether = scapy.Ether(packet)
+    else:
+        ether = packet
+
+    match = ofp.match()
+    parse_ether_layer(packet, match)
+    return match
diff --git a/Fabric/Utilities/src/python/oftest/pcap_writer.py b/Fabric/Utilities/src/python/oftest/pcap_writer.py
new file mode 100644
index 0000000..b405033
--- /dev/null
+++ b/Fabric/Utilities/src/python/oftest/pcap_writer.py
@@ -0,0 +1,77 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
+"""
+Pcap file writer
+"""
+
+import struct
+
+PcapHeader = struct.Struct("<LHHLLLL")
+PcapPktHeader = struct.Struct("<LLLL")
+PPIPktHeader = struct.Struct("<BBHL")
+PPIAggregateField = struct.Struct("<HHL")
+
+class PcapWriter(object):
+    def __init__(self, filename):
+        """
+        Open a pcap file
+        """
+        self.stream = file(filename, 'w')
+
+        self.stream.write(PcapHeader.pack(
+            0xa1b2c3d4, # magic
+            2, # major
+            4, # minor
+            0, # timezone offset
+            0, # timezone accuracy
+            65535, # snapshot length
+            192 # PPI linktype
+        ))
+
+    def write(self, data, timestamp, port):
+        """
+        Write a packet to a pcap file
+
+        'data' should be a string containing the packet data.
+        'timestamp' should be a float.
+        'port' should be an integer port number.
+        """
+        ppi_len = PPIPktHeader.size + PPIAggregateField.size
+        self.stream.write(PcapPktHeader.pack(
+            int(timestamp), # timestamp seconds
+            int((timestamp - int(timestamp)) * 10**6), # timestamp microseconds
+            len(data) + ppi_len, # truncated length
+            len(data) + ppi_len # un-truncated length
+        ))
+        self.stream.write(PPIPktHeader.pack(
+            0, # version
+            0, # flags
+            ppi_len, # length
+            1, # ethernet dlt
+        ))
+        self.stream.write(PPIAggregateField.pack(8, PPIAggregateField.size - 4, port))
+        self.stream.write(data)
+
+    def close(self):
+        self.stream.close()
+
+if __name__ == "__main__":
+    import time
+    print("Writing test pcap to test.pcap")
+    pcap_writer = PcapWriter("test.pcap")
+    pcap_writer.write("\x00\x01\x02\x03\x04\x05\x00\x0a\x0b\x0c\x0d\x0e\x08\x00", time.time(), 42)
+    pcap_writer.close()
diff --git a/Fabric/Utilities/src/python/oftest/test_parse.py b/Fabric/Utilities/src/python/oftest/test_parse.py
new file mode 100755
index 0000000..d4762ef
--- /dev/null
+++ b/Fabric/Utilities/src/python/oftest/test_parse.py
@@ -0,0 +1,195 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
+#!/usr/bin/env python
+import unittest
+import parse
+import packet as scapy
+
+class TestPacketToFlowMatchV3(unittest.TestCase):
+    def test_tcp(self):
+        import loxi.of12 as ofp
+        self.maxDiff = None
+        pkt = scapy.Ether(dst='00:01:02:03:04:05', src='00:06:07:08:09:0a')/ \
+            scapy.IP(src='192.168.0.1', dst='192.168.0.2', tos=2 | (32 << 2), ttl=64)/ \
+            scapy.TCP(sport=1234, dport=80)
+        expected = [
+            ofp.oxm.eth_dst([0x00, 0x01, 0x02, 0x03, 0x04, 0x05]),
+            ofp.oxm.eth_src([0x00, 0x06, 0x07, 0x08, 0x09, 0x0a]),
+            ofp.oxm.eth_type(0x0800),
+            ofp.oxm.vlan_vid(ofp.OFP_VLAN_NONE),
+            ofp.oxm.ip_proto(6),
+            ofp.oxm.ip_dscp(32),
+            ofp.oxm.ip_ecn(2),
+            ofp.oxm.ipv4_src(0xc0a80001),
+            ofp.oxm.ipv4_dst(0xc0a80002),
+            ofp.oxm.tcp_src(1234),
+            ofp.oxm.tcp_dst(80)
+        ]
+        result = parse.packet_to_flow_match_v3(pkt).oxm_list
+        self.assertEquals([x.show() for x in expected], [x.show() for x in result])
+
+    def test_udp(self):
+        import loxi.of12 as ofp
+        self.maxDiff = None
+        pkt = scapy.Ether(dst='00:01:02:03:04:05', src='00:06:07:08:09:0a')/ \
+            scapy.IP(src='192.168.0.1', dst='192.168.0.2', tos=2 | (32 << 2), ttl=64)/ \
+            scapy.UDP(sport=1234, dport=80)
+        expected = [
+            ofp.oxm.eth_dst([0x00, 0x01, 0x02, 0x03, 0x04, 0x05]),
+            ofp.oxm.eth_src([0x00, 0x06, 0x07, 0x08, 0x09, 0x0a]),
+            ofp.oxm.eth_type(0x0800),
+            ofp.oxm.vlan_vid(ofp.OFP_VLAN_NONE),
+            ofp.oxm.ip_proto(17),
+            ofp.oxm.ip_dscp(32),
+            ofp.oxm.ip_ecn(2),
+            ofp.oxm.ipv4_src(0xc0a80001),
+            ofp.oxm.ipv4_dst(0xc0a80002),
+            ofp.oxm.udp_src(1234),
+            ofp.oxm.udp_dst(80)
+        ]
+        result = parse.packet_to_flow_match_v3(pkt).oxm_list
+        self.assertEquals([x.show() for x in expected], [x.show() for x in result])
+
+    def test_icmp(self):
+        import loxi.of12 as ofp
+        self.maxDiff = None
+        pkt = scapy.Ether(dst='00:01:02:03:04:05', src='00:06:07:08:09:0a')/ \
+            scapy.IP(src='192.168.0.1', dst='192.168.0.2', tos=2 | (32 << 2), ttl=64)/ \
+            scapy.ICMP(type=8, code=1)
+        expected = [
+            ofp.oxm.eth_dst([0x00, 0x01, 0x02, 0x03, 0x04, 0x05]),
+            ofp.oxm.eth_src([0x00, 0x06, 0x07, 0x08, 0x09, 0x0a]),
+            ofp.oxm.eth_type(0x0800),
+            ofp.oxm.vlan_vid(ofp.OFP_VLAN_NONE),
+            ofp.oxm.ip_proto(1),
+            ofp.oxm.ip_dscp(32),
+            ofp.oxm.ip_ecn(2),
+            ofp.oxm.ipv4_src(0xc0a80001),
+            ofp.oxm.ipv4_dst(0xc0a80002),
+            ofp.oxm.icmpv4_type(8),
+            ofp.oxm.icmpv4_code(1)
+        ]
+        result = parse.packet_to_flow_match_v3(pkt).oxm_list
+        self.assertEquals([x.show() for x in expected], [x.show() for x in result])
+
+    def test_arp(self):
+        import loxi.of12 as ofp
+        self.maxDiff = None
+        pkt = scapy.Ether(dst='00:01:02:03:04:05', src='00:06:07:08:09:0a')/ \
+            scapy.ARP(hwsrc='00:01:02:03:04:05', hwdst='00:06:07:08:09:0a', \
+                      psrc='192.168.0.1', pdst='192.168.0.2', op=1)
+        expected = [
+            ofp.oxm.eth_dst([0x00, 0x01, 0x02, 0x03, 0x04, 0x05]),
+            ofp.oxm.eth_src([0x00, 0x06, 0x07, 0x08, 0x09, 0x0a]),
+            ofp.oxm.eth_type(0x0806),
+            ofp.oxm.vlan_vid(ofp.OFP_VLAN_NONE),
+            ofp.oxm.arp_op(1),
+            ofp.oxm.arp_spa(0xc0a80001),
+            ofp.oxm.arp_tpa(0xc0a80002),
+            ofp.oxm.arp_sha([0x00, 0x01, 0x02, 0x03, 0x04, 0x05]),
+            ofp.oxm.arp_tha([0x00, 0x06, 0x07, 0x08, 0x09, 0x0a]),
+        ]
+        result = parse.packet_to_flow_match_v3(pkt).oxm_list
+        self.assertEquals([x.show() for x in expected], [x.show() for x in result])
+
+    def test_tcpv6(self):
+        import loxi.of12 as ofp
+        self.maxDiff = None
+        pkt = scapy.Ether(dst='00:01:02:03:04:05', src='00:06:07:08:09:0a')/ \
+            scapy.IPv6(src="::1", dst="::2", nh=6, tc=2 | (32 << 2), fl=7)/ \
+            scapy.TCP(sport=1234, dport=80)
+        expected = [
+            ofp.oxm.eth_dst([0x00, 0x01, 0x02, 0x03, 0x04, 0x05]),
+            ofp.oxm.eth_src([0x00, 0x06, 0x07, 0x08, 0x09, 0x0a]),
+            ofp.oxm.eth_type(0x86dd),
+            ofp.oxm.vlan_vid(ofp.OFP_VLAN_NONE),
+            ofp.oxm.ip_proto(6),
+            ofp.oxm.ip_dscp(32),
+            ofp.oxm.ip_ecn(2),
+            ofp.oxm.ipv6_src("\x00" * 15 + "\x01"),
+            ofp.oxm.ipv6_dst("\x00" * 15 + "\x02"),
+            ofp.oxm.ipv6_flabel(7),
+            ofp.oxm.tcp_src(1234),
+            ofp.oxm.tcp_dst(80)
+        ]
+        result = parse.packet_to_flow_match_v3(pkt).oxm_list
+        self.assertEquals([x.show() for x in expected], [x.show() for x in result])
+
+    def test_icmpv6(self):
+        import loxi.of12 as ofp
+        self.maxDiff = None
+        pkt = scapy.Ether(dst='00:01:02:03:04:05', src='00:06:07:08:09:0a')/ \
+            scapy.IPv6(src="::1", dst="::2", tc=2 | (32 << 2), fl=7)/ \
+            scapy.ICMPv6EchoRequest()
+        expected = [
+            ofp.oxm.eth_dst([0x00, 0x01, 0x02, 0x03, 0x04, 0x05]),
+            ofp.oxm.eth_src([0x00, 0x06, 0x07, 0x08, 0x09, 0x0a]),
+            ofp.oxm.eth_type(0x86dd),
+            ofp.oxm.vlan_vid(ofp.OFP_VLAN_NONE),
+            ofp.oxm.ip_proto(0x3a),
+            ofp.oxm.ip_dscp(32),
+            ofp.oxm.ip_ecn(2),
+            ofp.oxm.ipv6_src("\x00" * 15 + "\x01"),
+            ofp.oxm.ipv6_dst("\x00" * 15 + "\x02"),
+            ofp.oxm.ipv6_flabel(7),
+            ofp.oxm.icmpv6_type(128),
+            ofp.oxm.icmpv6_code(0)
+        ]
+        result = parse.packet_to_flow_match_v3(pkt).oxm_list
+        self.assertEquals([x.show() for x in expected], [x.show() for x in result])
+
+    def test_vlan(self):
+        import loxi.of12 as ofp
+        self.maxDiff = None
+        pkt = scapy.Ether(dst='00:01:02:03:04:05', src='00:06:07:08:09:0a')/ \
+            scapy.Dot1Q(vlan=50, prio=5)/ \
+            scapy.IP(src='192.168.0.1', dst='192.168.0.2', tos=2 | (32 << 2), ttl=64)/ \
+            scapy.TCP(sport=1234, dport=80)
+        expected = [
+            ofp.oxm.eth_dst([0x00, 0x01, 0x02, 0x03, 0x04, 0x05]),
+            ofp.oxm.eth_src([0x00, 0x06, 0x07, 0x08, 0x09, 0x0a]),
+            ofp.oxm.eth_type(0x0800),
+            ofp.oxm.vlan_vid(50),
+            ofp.oxm.vlan_pcp(5),
+            ofp.oxm.ip_proto(6),
+            ofp.oxm.ip_dscp(32),
+            ofp.oxm.ip_ecn(2),
+            ofp.oxm.ipv4_src(0xc0a80001),
+            ofp.oxm.ipv4_dst(0xc0a80002),
+            ofp.oxm.tcp_src(1234),
+            ofp.oxm.tcp_dst(80)
+        ]
+        result = parse.packet_to_flow_match_v3(pkt).oxm_list
+        self.assertEquals([x.show() for x in expected], [x.show() for x in result])
+
+    def test_unknown_ethertype(self):
+        import loxi.of12 as ofp
+        self.maxDiff = None
+        pkt = scapy.Ether(dst='00:01:02:03:04:05', src='00:06:07:08:09:0a', type=0x0801)/ \
+            ('\x11' * 20)
+        expected = [
+            ofp.oxm.eth_dst([0x00, 0x01, 0x02, 0x03, 0x04, 0x05]),
+            ofp.oxm.eth_src([0x00, 0x06, 0x07, 0x08, 0x09, 0x0a]),
+            ofp.oxm.eth_type(0x0801),
+            ofp.oxm.vlan_vid(ofp.OFP_VLAN_NONE),
+        ]
+        result = parse.packet_to_flow_match_v3(pkt).oxm_list
+        self.assertEquals([x.show() for x in expected], [x.show() for x in result])
+
+
+if __name__ == '__main__':
+    unittest.main(verbosity=2)
diff --git a/Fabric/Utilities/src/python/oftest/testutils.py b/Fabric/Utilities/src/python/oftest/testutils.py
new file mode 100755
index 0000000..3a147c7
--- /dev/null
+++ b/Fabric/Utilities/src/python/oftest/testutils.py
@@ -0,0 +1,2262 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
+import sys
+import copy
+import logging
+import types
+import time
+import re
+from Queue import Queue
+
+import packet as scapy
+
+import oftest
+import oftest.controller
+import oftest.dataplane
+import oftest.parse
+import oftest.ofutils
+import ofp
+
+global skipped_test_count
+skipped_test_count = 0
+
+_import_blacklist = set(locals().keys())
+
+# Some useful defines
+IP_ETHERTYPE = 0x800
+TCP_PROTOCOL = 0x6
+UDP_PROTOCOL = 0x11
+
+MINSIZE = 0
+
+def delete_all_flows(ctrl, send_barrier=True):
+    """
+    Delete all flows on the switch
+    @param ctrl The controller object for the test
+    @param send_barrier Whether or not to send a barrier message
+    """
+
+    logging.info("Deleting all flows")
+    msg = ofp.message.flow_delete()
+    if ofp.OFP_VERSION in [1, 2]:
+        msg.match.wildcards = ofp.OFPFW_ALL
+        msg.out_port = ofp.OFPP_NONE
+        msg.buffer_id = 0xffffffff
+    elif ofp.OFP_VERSION >= 3:
+        msg.table_id = ofp.OFPTT_ALL
+        msg.buffer_id = ofp.OFP_NO_BUFFER
+        msg.out_port = ofp.OFPP_ANY
+        msg.out_group = ofp.OFPG_ANY
+    ctrl.message_send(msg)
+    if send_barrier:
+        do_barrier(ctrl)
+    return 0 # for backwards compatibility
+
+def delete_all_groups(ctrl):
+    """
+    Delete all groups on the switch
+    @param ctrl The controller object for the test
+    """
+
+    logging.info("Deleting all groups")
+    msg = ofp.message.group_delete(group_id=ofp.OFPG_ALL)
+    ctrl.message_send(msg)
+    do_barrier(ctrl)
+
+def delete_groups(ctrl, group_queue=Queue()):
+    """
+    Delete all groups on list
+    @param ctrl The controller object for the test
+    :param group_queue:
+    """
+    logging.info("Deleting groups")
+    while (not group_queue.empty()):
+        msg = ofp.message.group_delete(group_id=group_queue.get())
+        ctrl.message_send(msg)
+        do_barrier(ctrl)
+
+def delete_group(ctrl, group_id):
+    """
+    Delete a single group
+    @param ctrl The controller object for the test
+    :param group_id
+    """
+    logging.info("Deleting a single group with groupId:" + str(group_id))
+    msg = ofp.message.group_delete(group_id=group_id)
+    ctrl.message_send(msg)
+    do_barrier(ctrl)
+
+def required_wildcards(parent):
+    w = test_param_get('required_wildcards', default='default')
+    if w == 'l3-l4':
+        return (ofp.OFPFW_NW_SRC_ALL | ofp.OFPFW_NW_DST_ALL | ofp.OFPFW_NW_TOS
+                | ofp.OFPFW_NW_PROTO | ofp.OFPFW_TP_SRC | ofp.OFPFW_TP_DST)
+    else:
+        return 0
+
+def simple_packet(content='00 00 00 11 33 55 00 00 00 11 22 33 81 00 00 03 '
+                '08 00 45 00 00 2e 04 d2 00 00 7f 00 b2 47 c0 a8 '
+                '01 64 c0 a8 02 02 00 00 00 00 00 00 00 00 00 00 '
+                '00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00'):
+
+    pkt = ''.join(content.split(" ")).decode('hex')
+    pkt = scapy.Ether(pkt)
+    if len(pkt) < 64:
+        pkt = pkt/("D" * (64 - len(pkt)))
+    #scapy.hexdump(pkt)
+    return pkt
+
+def simple_tcp_packet(pktlen=100,
+                      eth_dst='00:01:02:03:04:05',
+                      eth_src='00:06:07:08:09:0a',
+                      dl_vlan_enable=False,
+                      vlan_vid=0,
+                      outer_vlan=None,
+                      vlan_pcp=0,
+                      dl_vlan_cfi=0,
+                      ip_src='192.168.0.1',
+                      ip_dst='192.168.0.2',
+                      ip_tos=0,
+                      ip_ttl=64,
+                      tcp_sport=1234,
+                      tcp_dport=80,
+                      tcp_flags="S",
+                      ip_ihl=None,
+                      ip_options=False
+                      ):
+    """
+    Return a simple dataplane TCP packet
+
+    Supports a few parameters:
+    @param len Length of packet in bytes w/o CRC
+    @param eth_dst Destinatino MAC
+    @param eth_src Source MAC
+    @param dl_vlan_enable True if the packet is with vlan, False otherwise
+    @param vlan_vid VLAN ID
+    @param vlan_pcp VLAN priority
+    @param ip_src IP source
+    @param ip_dst IP destination
+    @param ip_tos IP ToS
+    @param ip_ttl IP TTL
+    @param tcp_dport TCP destination port
+    @param tcp_sport TCP source port
+    @param tcp_flags TCP Control flags
+
+    Generates a simple TCP request.  Users
+    shouldn't assume anything about this packet other than that
+    it is a valid ethernet/IP/TCP frame.
+    """
+
+    if MINSIZE > pktlen:
+        pktlen = MINSIZE
+
+    # Note Dot1Q.id is really CFI
+    if (dl_vlan_enable):
+        pkt = scapy.Ether( dst=eth_dst, src=eth_src )
+        if outer_vlan:
+            pkt = pkt/scapy.Dot1Q(prio=vlan_pcp, id=dl_vlan_cfi, vlan=outer_vlan)
+
+        pkt = pkt/scapy.Dot1Q( prio=vlan_pcp, id=dl_vlan_cfi, vlan=vlan_vid )/ \
+              scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+              scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+    else:
+        if not ip_options:
+            pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+                scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+                scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+        else:
+            pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+                scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl, options=ip_options)/ \
+                scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+
+    pkt = pkt/("D" * (pktlen - len(pkt)))
+
+    return pkt
+
+def simple_tcpv6_packet(pktlen=100,
+                        eth_dst='00:01:02:03:04:05',
+                        eth_src='00:06:07:08:09:0a',
+                        dl_vlan_enable=False,
+                        vlan_vid=0,
+                        vlan_pcp=0,
+                        ipv6_src='2001:db8:85a3::8a2e:370:7334',
+                        ipv6_dst='2001:db8:85a3::8a2e:370:7335',
+                        ipv6_tc=0,
+                        ipv6_hlim=64,
+                        ipv6_fl=0,
+                        tcp_sport=1234,
+                        tcp_dport=80,
+                        tcp_flags="S"):
+    """
+    Return a simple IPv6/TCP packet
+
+    Supports a few parameters:
+    @param len Length of packet in bytes w/o CRC
+    @param eth_dst Destination MAC
+    @param eth_src Source MAC
+    @param dl_vlan_enable True if the packet is with vlan, False otherwise
+    @param vlan_vid VLAN ID
+    @param vlan_pcp VLAN priority
+    @param ipv6_src IPv6 source
+    @param ipv6_dst IPv6 destination
+    @param ipv6_tc IPv6 traffic class
+    @param ipv6_ttl IPv6 hop limit
+    @param ipv6_fl IPv6 flow label
+    @param tcp_dport TCP destination port
+    @param tcp_sport TCP source port
+    @param tcp_flags TCP Control flags
+
+    Generates a simple TCP request. Users shouldn't assume anything about this
+    packet other than that it is a valid ethernet/IPv6/TCP frame.
+    """
+
+    if MINSIZE > pktlen:
+        pktlen = MINSIZE
+
+    pkt = scapy.Ether(dst=eth_dst, src=eth_src)
+    if dl_vlan_enable or vlan_vid or vlan_pcp:
+        pkt /= scapy.Dot1Q(vlan=vlan_vid, prio=vlan_pcp)
+    pkt /= scapy.IPv6(src=ipv6_src, dst=ipv6_dst, fl=ipv6_fl, tc=ipv6_tc, hlim=ipv6_hlim)
+    pkt /= scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+    pkt /= ("D" * (pktlen - len(pkt)))
+
+    return pkt
+
+def simple_udp_packet(pktlen=100,
+                      eth_dst='00:01:02:03:04:05',
+                      eth_src='00:06:07:08:09:0a',
+                      dl_vlan_enable=False,
+                      vlan_vid=0,
+                      vlan_pcp=0,
+                      dl_vlan_cfi=0,
+                      ip_src='192.168.0.1',
+                      ip_dst='192.168.0.2',
+                      ip_tos=0,
+                      ip_ttl=64,
+                      udp_sport=1234,
+                      udp_dport=80,
+                      ip_ihl=None,
+                      ip_options=False
+                      ):
+    """
+    Return a simple dataplane UDP packet
+
+    Supports a few parameters:
+    @param len Length of packet in bytes w/o CRC
+    @param eth_dst Destination MAC
+    @param eth_src Source MAC
+    @param dl_vlan_enable True if the packet is with vlan, False otherwise
+    @param vlan_vid VLAN ID
+    @param vlan_pcp VLAN priority
+    @param ip_src IP source
+    @param ip_dst IP destination
+    @param ip_tos IP ToS
+    @param ip_ttl IP TTL
+    @param udp_dport UDP destination port
+    @param udp_sport UDP source port
+
+    Generates a simple UDP packet. Users shouldn't assume anything about
+    this packet other than that it is a valid ethernet/IP/UDP frame.
+    """
+
+    if MINSIZE > pktlen:
+        pktlen = MINSIZE
+
+    # Note Dot1Q.id is really CFI
+    if (dl_vlan_enable):
+        pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+            scapy.Dot1Q(prio=vlan_pcp, id=dl_vlan_cfi, vlan=vlan_vid)/ \
+            scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+            scapy.UDP(sport=udp_sport, dport=udp_dport)
+    else:
+        if not ip_options:
+            pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+                scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+                scapy.UDP(sport=udp_sport, dport=udp_dport)
+        else:
+            pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+                scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl, options=ip_options)/ \
+                scapy.UDP(sport=udp_sport, dport=udp_dport)
+
+    pkt = pkt/("D" * (pktlen - len(pkt)))
+
+    return pkt
+
+
+def simple_tcp_packet_two_vlan(pktlen=100,
+                      eth_dst='00:01:02:03:04:05',
+                      eth_src='00:06:07:08:09:0a',
+                      out_dl_vlan_enable=False,
+                      in_dl_vlan_enable=False,
+                      out_vlan_vid=0,
+                      out_vlan_pcp=0,
+                      out_vlan_tpid=0x8100,
+                      out_dl_vlan_cfi=0,
+                      in_vlan_vid=0,
+                      in_vlan_pcp=0,
+                      in_dl_vlan_cfi=0,
+                      ip_src='192.168.0.1',
+                      ip_dst='192.168.0.2',
+                      ip_tos=0,
+                      ip_ttl=64,
+                      tcp_sport=1234,
+                      tcp_dport=80,
+                      tcp_flags="S",
+                      ip_ihl=None,
+                      ip_options=False
+                      ):
+    """
+    Return a simple dataplane TCP packet
+
+    Supports a few parameters:
+    @param len Length of packet in bytes w/o CRC
+    @param eth_dst Destinatino MAC
+    @param eth_src Source MAC
+    @param dl_vlan_enable True if the packet is with vlan, False otherwise
+    @param vlan_vid VLAN ID
+    @param vlan_pcp VLAN priority
+    @param ip_src IP source
+    @param ip_dst IP destination
+    @param ip_tos IP ToS
+    @param ip_ttl IP TTL
+    @param tcp_dport TCP destination port
+    @param tcp_sport TCP source port
+    @param tcp_flags TCP Control flags
+
+    Generates a simple TCP request.  Users
+    shouldn't assume anything about this packet other than that
+    it is a valid ethernet/IP/TCP frame.
+    """
+
+    if MINSIZE > pktlen:
+        pktlen = MINSIZE
+
+    # Note Dot1Q.id is really CFI
+    if (out_dl_vlan_enable and in_dl_vlan_enable):
+        pkt = scapy.Ether(dst=eth_dst, src=eth_src, type=out_vlan_tpid)/ \
+            scapy.Dot1Q(prio=out_vlan_pcp, id=out_dl_vlan_cfi, vlan=out_vlan_vid)
+
+        if in_dl_vlan_enable:
+            pkt = pkt/scapy.Dot1Q(prio=in_vlan_pcp, id=in_dl_vlan_cfi, vlan=in_vlan_vid)
+
+        pkt = pkt/scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+            scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+    elif (out_dl_vlan_enable and (not in_dl_vlan_enable)):
+        pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+            scapy.Dot1Q(prio=out_vlan_pcp, id=out_dl_vlan_cfi, vlan=out_vlan_vid)/ \
+            scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+            scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+    elif ((not out_dl_vlan_enable) and in_dl_vlan_enable):
+        assert(0) #shall not have this caes
+    else:
+        if not ip_options:
+            pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+                scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+                scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+        else:
+            pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+                scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl, options=ip_options)/ \
+                scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+
+    pkt = pkt/("D" * (pktlen - len(pkt)))
+
+    return pkt
+
+def simple_vxlan_packet(eth_dst='00:01:02:03:04:05',
+                      eth_src='00:06:07:08:09:0a',
+                      dl_vlan_enable=False,
+                      vlan_vid=0,
+                      vlan_pcp=0,
+                      dl_vlan_cfi=0,
+                      ip_src='192.168.0.1',
+                      ip_dst='192.168.0.2',
+                      ip_tos=0,
+                      ip_ttl=64,
+                      udp_sport=1234,
+                      udp_dport=4789,
+                      vnid=1,
+                      inner_payload=None,
+                      ip_ihl=None,
+                      ip_options=False
+                      ):
+    """
+    Return a simple dataplane UDP packet
+
+    Supports a few parameters:
+    @param len Length of packet in bytes w/o CRC
+    @param eth_dst Destination MAC
+    @param eth_src Source MAC
+    @param dl_vlan_enable True if the packet is with vlan, False otherwise
+    @param vlan_vid VLAN ID
+    @param vlan_pcp VLAN priority
+    @param ip_src IP source
+    @param ip_dst IP destination
+    @param ip_tos IP ToS
+    @param ip_ttl IP TTL
+    @param udp_dport UDP destination port
+    @param udp_sport UDP source port
+    @param inner_pyload inner pacekt content
+    Generates a simple UDP packet. Users shouldn't assume anything about
+    this packet other than that it is a valid ethernet/IP/UDP frame.
+    """
+
+    # Note Dot1Q.id is really CFI
+    if (dl_vlan_enable):
+        pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+            scapy.Dot1Q(prio=vlan_pcp, id=dl_vlan_cfi, vlan=vlan_vid)/ \
+            scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+            scapy.UDP(sport=udp_sport, dport=udp_dport)
+    else:
+        if not ip_options:
+            pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+                scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+                scapy.UDP(sport=udp_sport, dport=udp_dport)
+        else:
+            pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+                scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl, options=ip_options)/ \
+                scapy.UDP(sport=udp_sport, dport=udp_dport)
+
+    #add vxlan header
+    pkt = pkt/scapy.VXLAN(vni=vnid)
+    #add innder payload
+    if inner_payload!=None:
+        pkt=pkt/inner_payload
+
+    return pkt
+
+def mpls_packet(pktlen=100,
+                      eth_dst='00:01:02:03:04:05',
+                      eth_src='00:06:07:08:09:0a',
+                      dl_vlan_enable=False,
+                      vlan_vid=0,
+                      vlan_pcp=0,
+                      dl_vlan_cfi=0,
+                      ip_src='192.168.0.1',
+                      ip_dst='192.168.0.2',
+                      ip_tos=0,
+                      ip_ttl=64,
+                      tcp_sport=1234,
+                      tcp_dport=80,
+                      tcp_flags="S",
+                      ip_ihl=None,
+                      ip_options=False,
+                      label=None,
+                      inner_payload=True,
+		      encapsulated_ethernet=False,
+		      encapsulated_eth_src='01:02:03:04:05:11',
+		      encapsulated_eth_dst='01:02:03:04:05:22'
+                      ):
+    if MINSIZE > pktlen:
+        pktlen = MINSIZE
+
+    # Note Dot1Q.id is really CFI
+    if (dl_vlan_enable):
+        pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+            scapy.Dot1Q(prio=vlan_pcp, id=dl_vlan_cfi, vlan=vlan_vid)
+    else:
+        pkt = scapy.Ether(dst=eth_dst, src=eth_src)
+
+    #add MPLS header
+    for i in range(len(label)):
+        l,c,s,t=label[i]
+        pkt = pkt/scapy.MPLS(label=l, cos=c, s=s, ttl=t)
+
+    #add innder payload
+    if inner_payload!=None:
+        if not encapsulated_ethernet:
+        	pkt=pkt / \
+           	   scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+            	   scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+        else:
+	       	pkt=pkt / \
+		   scapy.Ether(dst=encapsulated_eth_dst, src=encapsulated_eth_src)/ \
+           	   scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+            	   scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+
+    pkt = pkt/("D" * (pktlen - len(pkt)))
+
+    return pkt
+
+def pw_packet(pktlen=100,
+                      out_eth_dst='00:01:02:03:04:05',
+                      out_eth_src='00:06:07:08:09:0a',
+                      label=None,
+                      cw=None,
+                      in_eth_dst='00:01:02:03:04:05',
+                      in_eth_src='00:06:07:08:09:0a',
+                      out_dl_vlan_enable=False,
+                      in_dl_vlan_enable=False,
+                      out_vlan_vid=0,
+                      out_vlan_pcp=0,
+                      out_dl_vlan_cfi=0,
+                      in_vlan_vid=0,
+                      in_vlan_pcp=0,
+                      in_dl_vlan_cfi=0,
+                      ip_src='192.168.0.1',
+                      ip_dst='192.168.0.2',
+                      ip_tos=0,
+                      ip_ttl=64,
+                      tcp_sport=1234,
+                      tcp_dport=80,
+                      tcp_flags="S",
+                      ip_ihl=None,
+                      ip_options=False
+                      ):
+    """
+    Return a simple dataplane TCP packet encapsulated
+    in a pw packet
+    """
+
+    # Add the outer ethernet header
+    if MINSIZE > pktlen:
+        pktlen = MINSIZE
+
+    pkt = scapy.Ether(dst=out_eth_dst, src=out_eth_src)
+
+    #add MPLS header
+    for i in range(len(label)):
+        l,c,s,t=label[i]
+        pkt = pkt/scapy.MPLS(label=l, cos=c, s=s, ttl=t)
+
+    #add the PW CW
+    l,c,s,t=cw
+    pkt = pkt/scapy.MPLS(label=l, cos=c, s=s, ttl=t)
+
+    # Note Dot1Q.id is really CFI
+    if (out_dl_vlan_enable and in_dl_vlan_enable):
+
+        pkt = pkt/scapy.Ether(dst=in_eth_dst, src=in_eth_src)/ \
+            scapy.Dot1Q(prio=out_vlan_pcp, id=out_dl_vlan_cfi, vlan=out_vlan_vid)
+
+        pkt = pkt/scapy.Dot1Q(prio=in_vlan_pcp, id=in_dl_vlan_cfi, vlan=in_vlan_vid)
+
+        pkt = pkt/scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+            scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+
+    elif (out_dl_vlan_enable and (not in_dl_vlan_enable)):
+
+        pkt = pkt/scapy.Ether(dst=in_eth_dst, src=in_eth_src)/ \
+            scapy.Dot1Q(prio=out_vlan_pcp, id=out_dl_vlan_cfi, vlan=out_vlan_vid)/ \
+            scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+            scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+
+    elif ((not out_dl_vlan_enable) and in_dl_vlan_enable):
+
+        assert(0) #shall not have this caes
+
+    else:
+        if not ip_options:
+            pkt = pkt/scapy.Ether(dst=in_eth_dst, src=in_eth_src)/ \
+                scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+                scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+        else:
+            pkt = pkt/scapy.Ether(dst=in_eth_dst, src=in_eth_src)/ \
+                scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl, options=ip_options)/ \
+                scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+
+    pkt = pkt/("D" * (pktlen - len(pkt)))
+
+    return pkt
+
+def mplsv6_packet(pktlen=100,
+                      eth_dst='00:01:02:03:04:05',
+                      eth_src='00:06:07:08:09:0a',
+                      dl_vlan_enable=False,
+                      vlan_vid=0,
+                      vlan_pcp=0,
+                      dl_vlan_cfi=0,
+                      ipv6_src='2001:db8:85a3::8a2e:370:7334',
+                      ipv6_dst='2001:db8:85a3::8a2e:370:7335',
+                      ipv6_tc=0,
+                      ipv6_hlim=64,
+                      ipv6_fl=0,
+                      tcp_sport=1234,
+                      tcp_dport=80,
+                      tcp_flags="S",
+                      label=None,
+                      inner_payload=True
+                      ):
+    if MINSIZE > pktlen:
+        pktlen = MINSIZE
+
+    # Note Dot1Q.id is really CFI
+    if (dl_vlan_enable):
+        pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+            scapy.Dot1Q(prio=vlan_pcp, id=dl_vlan_cfi, vlan=vlan_vid)
+    else:
+        pkt = scapy.Ether(dst=eth_dst, src=eth_src)
+
+    #add MPLS header
+    for i in range(len(label)):
+        l,c,s,t=label[i]
+        pkt = pkt/scapy.MPLS(label=l, cos=c, s=s, ttl=t)
+
+    #add innder payload
+    if inner_payload!=None:
+        pkt=pkt / \
+            scapy.IPv6(src=ipv6_src, dst=ipv6_dst, fl=ipv6_fl, tc=ipv6_tc, hlim=ipv6_hlim)/ \
+            scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+
+    pkt = pkt/("D" * (pktlen - len(pkt)))
+
+    return pkt
+
+def simple_mpls_packet(eth_dst='00:01:02:03:04:05',
+                      eth_src='00:06:07:08:09:0a',
+                      dl_vlan_enable=False,
+                      vlan_vid=0,
+                      vlan_pcp=0,
+                      dl_vlan_cfi=0,
+                      label=None,
+                      inner_payload=None
+                      ):
+    """
+    Return a simple dataplane MPLS packet
+
+    Supports a few parameters:
+    @param len Length of packet in bytes w/o CRC
+    @param eth_dst Destination MAC
+    @param eth_src Source MAC
+    @param dl_vlan_enable True if the packet is with vlan, False otherwise
+    @param vlan_vid VLAN ID
+    @param vlan_pcp VLAN priority
+    @param inner_pyload inner pacekt content
+    Generates a simple MPLS packet. Users shouldn't assume anything about
+    this packet other than that it is a valid ethernet/IP/UDP frame.
+    """
+
+    # Note Dot1Q.id is really CFI
+    if (dl_vlan_enable):
+        pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+            scapy.Dot1Q(prio=vlan_pcp, id=dl_vlan_cfi, vlan=vlan_vid)
+    else:
+        pkt = scapy.Ether(dst=eth_dst, src=eth_src)
+
+    #add MPLS header
+    for i in range(len(label)):
+        l,c,s,t=label[i]
+        pkt = pkt/scapy.MPLS(label=l, cos=c, s=s, ttl=t)
+
+    #add innder payload
+    if inner_payload!=None:
+        pkt=pkt/inner_payload
+
+    return pkt
+
+def simple_udpv6_packet(pktlen=100,
+                        eth_dst='00:01:02:03:04:05',
+                        eth_src='00:06:07:08:09:0a',
+                        dl_vlan_enable=False,
+                        vlan_vid=0,
+                        vlan_pcp=0,
+                        ipv6_src='2001:db8:85a3::8a2e:370:7334',
+                        ipv6_dst='2001:db8:85a3::8a2e:370:7335',
+                        ipv6_tc=0,
+                        ipv6_hlim=64,
+                        ipv6_fl=0,
+                        udp_sport=1234,
+                        udp_dport=80):
+    """
+    Return a simple IPv6/UDP packet
+
+    Supports a few parameters:
+    @param len Length of packet in bytes w/o CRC
+    @param eth_dst Destination MAC
+    @param eth_src Source MAC
+    @param dl_vlan_enable True if the packet is with vlan, False otherwise
+    @param vlan_vid VLAN ID
+    @param vlan_pcp VLAN priority
+    @param ipv6_src IPv6 source
+    @param ipv6_dst IPv6 destination
+    @param ipv6_tc IPv6 traffic class
+    @param ipv6_ttl IPv6 hop limit
+    @param ipv6_fl IPv6 flow label
+    @param udp_dport UDP destination port
+    @param udp_sport UDP source port
+
+    Generates a simple UDP request. Users shouldn't assume anything about this
+    packet other than that it is a valid ethernet/IPv6/UDP frame.
+    """
+
+    if MINSIZE > pktlen:
+        pktlen = MINSIZE
+
+    pkt = scapy.Ether(dst=eth_dst, src=eth_src)
+    if dl_vlan_enable or vlan_vid or vlan_pcp:
+        pkt /= scapy.Dot1Q(vlan=vlan_vid, prio=vlan_pcp)
+    pkt /= scapy.IPv6(src=ipv6_src, dst=ipv6_dst, fl=ipv6_fl, tc=ipv6_tc, hlim=ipv6_hlim)
+    pkt /= scapy.UDP(sport=udp_sport, dport=udp_dport)
+    pkt /= ("D" * (pktlen - len(pkt)))
+
+    return pkt
+
+def simple_icmp_packet(pktlen=60,
+                      eth_dst='00:01:02:03:04:05',
+                      eth_src='00:06:07:08:09:0a',
+                      dl_vlan_enable=False,
+                      vlan_vid=0,
+                      vlan_pcp=0,
+                      ip_src='192.168.0.1',
+                      ip_dst='192.168.0.2',
+                      ip_tos=0,
+                      ip_ttl=64,
+                      ip_id=1,
+                      icmp_type=8,
+                      icmp_code=0,
+                      icmp_data=''):
+    """
+    Return a simple ICMP packet
+
+    Supports a few parameters:
+    @param len Length of packet in bytes w/o CRC
+    @param eth_dst Destinatino MAC
+    @param eth_src Source MAC
+    @param dl_vlan_enable True if the packet is with vlan, False otherwise
+    @param vlan_vid VLAN ID
+    @param vlan_pcp VLAN priority
+    @param ip_src IP source
+    @param ip_dst IP destination
+    @param ip_tos IP ToS
+    @param ip_ttl IP TTL
+    @param ip_id IP Identification
+    @param icmp_type ICMP type
+    @param icmp_code ICMP code
+    @param icmp_data ICMP data
+
+    Generates a simple ICMP ECHO REQUEST.  Users
+    shouldn't assume anything about this packet other than that
+    it is a valid ethernet/ICMP frame.
+    """
+
+    if MINSIZE > pktlen:
+        pktlen = MINSIZE
+
+    if (dl_vlan_enable):
+        pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+            scapy.Dot1Q(prio=vlan_pcp, id=0, vlan=vlan_vid)/ \
+            scapy.IP(src=ip_src, dst=ip_dst, ttl=ip_ttl, tos=ip_tos, id=ip_id)/ \
+            scapy.ICMP(type=icmp_type, code=icmp_code)/ icmp_data
+    else:
+        pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+            scapy.IP(src=ip_src, dst=ip_dst, ttl=ip_ttl, tos=ip_tos, id=ip_id)/ \
+            scapy.ICMP(type=icmp_type, code=icmp_code)/ icmp_data
+
+    pkt = pkt/("0" * (pktlen - len(pkt)))
+
+    return pkt
+
+def simple_icmpv6_packet(pktlen=100,
+                         eth_dst='00:01:02:03:04:05',
+                         eth_src='00:06:07:08:09:0a',
+                         dl_vlan_enable=False,
+                         vlan_vid=0,
+                         vlan_pcp=0,
+                         ipv6_src='2001:db8:85a3::8a2e:370:7334',
+                         ipv6_dst='2001:db8:85a3::8a2e:370:7335',
+                         ipv6_tc=0,
+                         ipv6_hlim=64,
+                         ipv6_fl=0,
+                         icmp_type=8,
+                         icmp_code=0):
+    """
+    Return a simple ICMPv6 packet
+
+    Supports a few parameters:
+    @param len Length of packet in bytes w/o CRC
+    @param eth_dst Destination MAC
+    @param eth_src Source MAC
+    @param dl_vlan_enable True if the packet is with vlan, False otherwise
+    @param vlan_vid VLAN ID
+    @param vlan_pcp VLAN priority
+    @param ipv6_src IPv6 source
+    @param ipv6_dst IPv6 destination
+    @param ipv6_tc IPv6 traffic class
+    @param ipv6_ttl IPv6 hop limit
+    @param ipv6_fl IPv6 flow label
+    @param icmp_type ICMP type
+    @param icmp_code ICMP code
+
+    Generates a simple ICMP ECHO REQUEST. Users shouldn't assume anything
+    about this packet other than that it is a valid ethernet/IPv6/ICMP frame.
+    """
+
+    if MINSIZE > pktlen:
+        pktlen = MINSIZE
+
+    pkt = scapy.Ether(dst=eth_dst, src=eth_src)
+    if dl_vlan_enable or vlan_vid or vlan_pcp:
+        pkt /= scapy.Dot1Q(vlan=vlan_vid, prio=vlan_pcp)
+    pkt /= scapy.IPv6(src=ipv6_src, dst=ipv6_dst, fl=ipv6_fl, tc=ipv6_tc, hlim=ipv6_hlim)
+    pkt /= scapy.ICMPv6Unknown(type=icmp_type, code=icmp_code)
+    pkt /= ("D" * (pktlen - len(pkt)))
+
+    return pkt
+
+def simple_arp_packet(pktlen=60,
+                      eth_dst='ff:ff:ff:ff:ff:ff',
+                      eth_src='00:06:07:08:09:0a',
+                      vlan_vid=0,
+                      vlan_pcp=0,
+                      arp_op=1,
+                      ip_snd='192.168.0.1',
+                      ip_tgt='192.168.0.2',
+                      hw_snd='00:06:07:08:09:0a',
+                      hw_tgt='00:00:00:00:00:00',
+                      ):
+    """
+    Return a simple ARP packet
+
+    Supports a few parameters:
+    @param len Length of packet in bytes w/o CRC
+    @param eth_dst Destinatino MAC
+    @param eth_src Source MAC
+    @param arp_op Operation (1=request, 2=reply)
+    @param ip_snd Sender IP
+    @param ip_tgt Target IP
+    @param hw_snd Sender hardware address
+    @param hw_tgt Target hardware address
+
+    Generates a simple ARP REQUEST.  Users
+    shouldn't assume anything about this packet other than that
+    it is a valid ethernet/ARP frame.
+    """
+
+    if MINSIZE > pktlen:
+        pktlen = MINSIZE
+
+    pkt = scapy.Ether(dst=eth_dst, src=eth_src)
+    if vlan_vid or vlan_pcp:
+        pkt /= scapy.Dot1Q(vlan=vlan_vid, prio=vlan_pcp)
+    pkt /= scapy.ARP(hwsrc=hw_snd, hwdst=hw_tgt, pdst=ip_tgt, psrc=ip_snd, op=arp_op)
+
+    pkt = pkt/("\0" * (pktlen - len(pkt)))
+
+    return pkt
+
+def simple_eth_packet(pktlen=60,
+                      eth_dst='00:01:02:03:04:05',
+                      eth_src='00:06:07:08:09:0a',
+                      eth_type=0x88cc):
+
+    if MINSIZE > pktlen:
+        pktlen = MINSIZE
+
+    pkt = scapy.Ether(dst=eth_dst, src=eth_src, type=eth_type)
+
+    pkt = pkt/("0" * (pktlen - len(pkt)))
+
+    return pkt
+
+def qinq_tcp_packet(pktlen=100,
+                    eth_dst='00:01:02:03:04:05',
+                    eth_src='00:06:07:08:09:0a',
+                    dl_vlan_outer=20,
+                    dl_vlan_pcp_outer=0,
+                    dl_vlan_cfi_outer=0,
+                    vlan_vid=10,
+                    vlan_pcp=0,
+                    dl_vlan_cfi=0,
+                    ip_src='192.168.0.1',
+                    ip_dst='192.168.0.2',
+                    ip_tos=0,
+                    ip_ttl=64,
+                    tcp_sport=1234,
+                    tcp_dport=80,
+                    ip_ihl=None,
+                    ip_options=False
+                    ):
+    """
+    Return a doubly tagged dataplane TCP packet
+
+    Supports a few parameters:
+    @param len Length of packet in bytes w/o CRC
+    @param eth_dst Destinatino MAC
+    @param eth_src Source MAC
+    @param dl_vlan_outer Outer VLAN ID
+    @param dl_vlan_pcp_outer Outer VLAN priority
+    @param dl_vlan_cfi_outer Outer VLAN cfi bit
+    @param vlan_vid Inner VLAN ID
+    @param vlan_pcp VLAN priority
+    @param dl_vlan_cfi VLAN cfi bit
+    @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 TCP request.  Users
+    shouldn't assume anything about this packet other than that
+    it is a valid ethernet/IP/TCP frame.
+    """
+
+    if MINSIZE > pktlen:
+        pktlen = MINSIZE
+
+    # Note Dot1Q.id is really CFI
+    pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
+          scapy.Dot1Q(prio=dl_vlan_pcp_outer, id=dl_vlan_cfi_outer, vlan=dl_vlan_outer)/ \
+          scapy.Dot1Q(prio=vlan_pcp, id=dl_vlan_cfi, vlan=vlan_vid)/ \
+          scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+          scapy.TCP(sport=tcp_sport, dport=tcp_dport)
+
+    pkt = pkt/("D" * (pktlen - len(pkt)))
+
+    return pkt
+
+def qinq_packet(pktlen=100,
+                type=0x0800,
+                eth_dst='00:01:02:03:04:05',
+                eth_src='00:06:07:08:09:0a',
+                dl_vlan_outer=20,
+                dl_vlan_pcp_outer=0,
+                dl_vlan_cfi_outer=0,
+                vlan_vid=10,
+                vlan_pcp=0,
+                dl_vlan_cfi=0,
+                ):
+    """
+    Return a doubly tagged dataplane qinq packet
+
+    Supports a few parameters:
+    @param len Length of packet in bytes w/o CRC
+    @param type ethernet type
+    @param eth_dst Destinatino MAC
+    @param eth_src Source MAC
+    @param dl_vlan_outer Outer VLAN ID
+    @param dl_vlan_pcp_outer Outer VLAN priority
+    @param dl_vlan_cfi_outer Outer VLAN cfi bit
+    @param vlan_vid Inner VLAN ID
+    @param vlan_pcp VLAN priority
+    @param dl_vlan_cfi VLAN cfi bit
+
+    Generates a qinq request.
+    """
+
+    if MINSIZE > pktlen:
+        pktlen = MINSIZE
+
+    # Note Dot1Q.id is really CFI
+    pkt = scapy.Ether(dst=eth_dst, src=eth_src, type=type) / \
+          scapy.Dot1Q(prio=dl_vlan_pcp_outer, id=dl_vlan_cfi_outer, vlan=dl_vlan_outer) / \
+          scapy.Dot1Q(prio=vlan_pcp, id=dl_vlan_cfi, vlan=vlan_vid) / \
+          scapy.IP()
+
+    pkt = pkt / ("D" * (pktlen - len(pkt)))
+    return pkt
+
+def simple_ether_packet_two_vlan(pktlen=100,
+                                 eth_dst='00:01:02:03:04:05',
+                                 eth_src='00:06:07:08:09:0a',
+                                 out_dl_vlan_enable=True,
+                                 in_dl_vlan_enable=True,
+                                 out_vlan_vid=100,
+                                 out_vlan_pcp=0,
+                                 out_vlan_tpid=0x8100,
+                                 out_dl_vlan_cfi=0,
+                                 in_vlan_vid=100,
+                                 in_vlan_pcp=0,
+                                 in_vlan_tpid=0x0800,
+                                 in_dl_vlan_cfi=0):
+    """
+    Return a simple dataplane ether packet
+
+    Supports a few parameters:
+    @param len Length of packet in bytes w/o CRC
+    @param eth_dst Destinatino MAC
+    @param eth_src Source MAC
+    @param dl_vlan_enable True if the packet is with vlan, False otherwise
+    @param vlan_vid VLAN ID
+    @param vlan_pcp VLAN priority
+    """
+
+    if MINSIZE > pktlen:
+        pktlen = MINSIZE
+
+    if (out_dl_vlan_enable and in_dl_vlan_enable):
+        pkt = scapy.Ether(dst=eth_dst, src=eth_src, type=out_vlan_tpid) / \
+              scapy.Dot1Q(prio=out_vlan_pcp, id=out_dl_vlan_cfi, vlan=out_vlan_vid)
+        pkt = pkt / scapy.Dot1Q(prio=in_vlan_pcp, id=in_dl_vlan_cfi, vlan=in_vlan_vid, type=in_vlan_tpid)
+
+    elif (out_dl_vlan_enable and (not in_dl_vlan_enable)):
+        pkt = scapy.Ether(dst=eth_dst, src=eth_src) / \
+              scapy.Dot1Q(prio=out_vlan_pcp, id=out_dl_vlan_cfi, vlan=out_vlan_vid)
+
+    elif ((not out_dl_vlan_enable) and in_dl_vlan_enable):
+        assert (0)  # shall not have this case
+    else:
+        pkt = scapy.Ether(dst=eth_dst, src=eth_src)
+
+    pkt = pkt / ("D" * (pktlen - len(pkt)))
+    return pkt
+
+
+def do_barrier(ctrl, timeout=-1):
+    """
+    Do a barrier command
+    Return 0 on success, -1 on error
+    """
+    b = ofp.message.barrier_request()
+    (resp, pkt) = ctrl.transact(b, timeout=timeout)
+    if resp is None:
+        raise AssertionError("barrier failed")
+    # We'll trust the transaction processing in the controller that xid matched
+    return 0 # for backwards compatibility
+
+def port_config_get(controller, port_no):
+    """
+    Get a port's configuration
+
+    Gets the switch feature configuration and grabs one port's
+    configuration
+
+    @returns (hwaddr, config, advert) The hwaddress, configuration and
+    advertised values
+    """
+
+    if ofp.OFP_VERSION <= 3:
+        request = ofp.message.features_request()
+        reply, _ = controller.transact(request)
+        if reply is None:
+            logging.warn("Get feature request failed")
+            return None, None, None
+        logging.debug(reply.show())
+        ports = reply.ports
+    else:
+        request = ofp.message.port_desc_stats_request()
+        # TODO do multipart correctly
+        reply, _ = controller.transact(request)
+        if reply is None:
+            logging.warn("Port desc stats request failed")
+            return None, None, None
+        logging.debug(reply.show())
+        ports = reply.entries
+
+    for port in ports:
+        if port.port_no == port_no:
+            return (port.hw_addr, port.config, port.advertised)
+
+    logging.warn("Did not find port number for port config")
+    return None, None, None
+
+def port_config_set(controller, port_no, config, mask):
+    """
+    Set the port configuration according the given parameters
+
+    Gets the switch feature configuration and updates one port's
+    configuration value according to config and mask
+    """
+    logging.info("Setting port " + str(port_no) + " to config " + str(config))
+
+    hw_addr, _, _ = port_config_get(controller, port_no)
+
+    mod = ofp.message.port_mod()
+    mod.port_no = port_no
+    if hw_addr != None:
+        mod.hw_addr = hw_addr
+    mod.config = config
+    mod.mask = mask
+    mod.advertise = 0 # No change
+    controller.message_send(mod)
+    return 0
+
+def receive_pkt_check(dp, pkt, yes_ports, no_ports, assert_if):
+    """
+    Check for proper receive packets across all ports
+    @param dp The dataplane object
+    @param pkt Expected packet; may be None if yes_ports is empty
+    @param yes_ports Set or list of ports that should recieve packet
+    @param no_ports Set or list of ports that should not receive packet
+    @param assert_if Object that implements assertXXX
+
+    DEPRECATED in favor in verify_packets
+    """
+
+    exp_pkt_arg = None
+    if oftest.config["relax"]:
+        exp_pkt_arg = pkt
+
+    for ofport in yes_ports:
+        logging.debug("Checking for pkt on port " + str(ofport))
+        (rcv_port, rcv_pkt, pkt_time) = dp.poll(
+            port_number=ofport, exp_pkt=exp_pkt_arg)
+        assert_if.assertTrue(rcv_pkt is not None,
+                             "Did not receive pkt on " + str(ofport))
+        if not oftest.dataplane.match_exp_pkt(pkt, rcv_pkt):
+            logging.debug("Expected %s" % format_packet(pkt))
+            logging.debug("Received %s" % format_packet(rcv_pkt))
+        assert_if.assertTrue(oftest.dataplane.match_exp_pkt(pkt, rcv_pkt),
+                             "Received packet does not match expected packet " +
+                             "on port " + str(ofport))
+    if len(no_ports) > 0:
+        time.sleep(oftest.ofutils.default_negative_timeout)
+    for ofport in no_ports:
+        logging.debug("Negative check for pkt on port " + str(ofport))
+        (rcv_port, rcv_pkt, pkt_time) = dp.poll(
+            port_number=ofport, timeout=0, exp_pkt=exp_pkt_arg)
+        assert_if.assertTrue(rcv_pkt is None,
+                             "Unexpected pkt on port " + str(ofport))
+
+
+def receive_pkt_verify(parent, egr_ports, exp_pkt, ing_port):
+    """
+    Receive a packet and verify it matches an expected value
+    @param egr_port A single port or list of ports
+
+    parent must implement dataplane, assertTrue and assertEqual
+
+    DEPRECATED in favor in verify_packets
+    """
+    exp_pkt_arg = None
+    if oftest.config["relax"]:
+        exp_pkt_arg = exp_pkt
+
+    if type(egr_ports) == type([]):
+        egr_port_list = egr_ports
+    else:
+        egr_port_list = [egr_ports]
+
+    # Expect a packet from each port on egr port list
+    for egr_port in egr_port_list:
+        check_port = egr_port
+        if egr_port == ofp.OFPP_IN_PORT:
+            check_port = ing_port
+        (rcv_port, rcv_pkt, pkt_time) = parent.dataplane.poll(
+            port_number=check_port, exp_pkt=exp_pkt_arg)
+
+        if rcv_pkt is None:
+            logging.error("ERROR: No packet received from " +
+                                str(check_port))
+
+        parent.assertTrue(rcv_pkt is not None,
+                          "Did not receive packet port " + str(check_port))
+        logging.debug("Packet len " + str(len(rcv_pkt)) + " in on " +
+                            str(rcv_port))
+
+        if str(exp_pkt) != str(rcv_pkt):
+            logging.error("ERROR: Packet match failed.")
+            logging.debug("Expected len " + str(len(exp_pkt)) + ": "
+                                + str(exp_pkt).encode('hex'))
+            logging.debug("Received len " + str(len(rcv_pkt)) + ": "
+                                + str(rcv_pkt).encode('hex'))
+            logging.debug("Expected packet: " + inspect_packet(scapy.Ether(str(exp_pkt))))
+            logging.debug("Received packet: " + inspect_packet(scapy.Ether(str(rcv_pkt))))
+        parent.assertEqual(str(exp_pkt), str(rcv_pkt),
+                           "Packet match error on port " + str(check_port))
+
+def match_verify(parent, req_match, res_match):
+    """
+    Verify flow matches agree; if they disagree, report where
+
+    parent must implement assertEqual
+    Use str() to ensure content is compared and not pointers
+    """
+
+    parent.assertEqual(req_match.wildcards, res_match.wildcards,
+                       'Match failed: wildcards: ' + hex(req_match.wildcards) +
+                       " != " + hex(res_match.wildcards))
+    parent.assertEqual(req_match.in_port, res_match.in_port,
+                       'Match failed: in_port: ' + str(req_match.in_port) +
+                       " != " + str(res_match.in_port))
+    parent.assertEqual(str(req_match.eth_src), str(res_match.eth_src),
+                       'Match failed: eth_src: ' + str(req_match.eth_src) +
+                       " != " + str(res_match.eth_src))
+    parent.assertEqual(str(req_match.eth_dst), str(res_match.eth_dst),
+                       'Match failed: eth_dst: ' + str(req_match.eth_dst) +
+                       " != " + str(res_match.eth_dst))
+    parent.assertEqual(req_match.vlan_vid, res_match.vlan_vid,
+                       'Match failed: vlan_vid: ' + str(req_match.vlan_vid) +
+                       " != " + str(res_match.vlan_vid))
+    parent.assertEqual(req_match.vlan_pcp, res_match.vlan_pcp,
+                       'Match failed: vlan_pcp: ' +
+                       str(req_match.vlan_pcp) + " != " +
+                       str(res_match.vlan_pcp))
+    parent.assertEqual(req_match.eth_type, res_match.eth_type,
+                       'Match failed: eth_type: ' + str(req_match.eth_type) +
+                       " != " + str(res_match.eth_type))
+
+    if (not(req_match.wildcards & ofp.OFPFW_DL_TYPE)
+        and (req_match.eth_type == IP_ETHERTYPE)):
+        parent.assertEqual(req_match.ip_dscp, res_match.ip_dscp,
+                           'Match failed: ip_dscp: ' + str(req_match.ip_dscp) +
+                           " != " + str(res_match.ip_dscp))
+        parent.assertEqual(req_match.ip_proto, res_match.ip_proto,
+                           'Match failed: ip_proto: ' + str(req_match.ip_proto) +
+                           " != " + str(res_match.ip_proto))
+        parent.assertEqual(req_match.ipv4_src, res_match.ipv4_src,
+                           'Match failed: ipv4_src: ' + str(req_match.ipv4_src) +
+                           " != " + str(res_match.ipv4_src))
+        parent.assertEqual(req_match.ipv4_dst, res_match.ipv4_dst,
+                           'Match failed: ipv4_dst: ' + str(req_match.ipv4_dst) +
+                           " != " + str(res_match.ipv4_dst))
+
+        if (not(req_match.wildcards & ofp.OFPFW_NW_PROTO)
+            and ((req_match.ip_proto == TCP_PROTOCOL)
+                 or (req_match.ip_proto == UDP_PROTOCOL))):
+            parent.assertEqual(req_match.tcp_src, res_match.tcp_src,
+                               'Match failed: tcp_src: ' +
+                               str(req_match.tcp_src) +
+                               " != " + str(res_match.tcp_src))
+            parent.assertEqual(req_match.tcp_dst, res_match.tcp_dst,
+                               'Match failed: tcp_dst: ' +
+                               str(req_match.tcp_dst) +
+                               " != " + str(res_match.tcp_dst))
+
+def packet_to_flow_match(parent, packet):
+    match = oftest.parse.packet_to_flow_match(packet)
+    if ofp.OFP_VERSION in [1, 2]:
+        match.wildcards |= required_wildcards(parent)
+    else:
+        # TODO remove incompatible OXM entries
+        pass
+    return match
+
+def flow_msg_create(parent, pkt, ing_port=None, action_list=None, wildcards=None,
+               egr_ports=None, egr_queue=None, check_expire=False, in_band=False):
+    """
+    Create a flow message
+
+    Match on packet with given wildcards.
+    See flow_match_test for other parameter descriptoins
+    @param egr_queue if not None, make the output an enqueue action
+    @param in_band if True, do not wildcard ingress port
+    @param egr_ports None (drop), single port or list of ports
+    """
+    match = oftest.parse.packet_to_flow_match(pkt)
+    parent.assertTrue(match is not None, "Flow match from pkt failed")
+    if wildcards is None:
+        wildcards = required_wildcards(parent)
+    if in_band:
+        wildcards &= ~ofp.OFPFW_IN_PORT
+    match.wildcards = wildcards
+    match.in_port = ing_port
+
+    if type(egr_ports) == type([]):
+        egr_port_list = egr_ports
+    else:
+        egr_port_list = [egr_ports]
+
+    request = ofp.message.flow_add()
+    request.match = match
+    request.buffer_id = 0xffffffff
+    if check_expire:
+        request.flags |= ofp.OFPFF_SEND_FLOW_REM
+        request.hard_timeout = 1
+
+    if ofp.OFP_VERSION == 1:
+        actions = request.actions
+    else:
+        actions = []
+        request.instructions.append(ofp.instruction.apply_actions(actions))
+
+    if action_list is not None:
+        actions.extend(action_list)
+
+    # Set up output/enqueue action if directed
+    if egr_queue is not None:
+        parent.assertTrue(egr_ports is not None, "Egress port not set")
+        act = ofp.action.enqueue()
+        for egr_port in egr_port_list:
+            act.port = egr_port
+            act.queue_id = egr_queue
+            actions.append(act)
+    elif egr_ports is not None:
+        for egr_port in egr_port_list:
+            act = ofp.action.output()
+            act.port = egr_port
+            actions.append(act)
+
+    logging.debug(request.show())
+
+    return request
+
+def flow_msg_install(parent, request, clear_table_override=None):
+    """
+    Install a flow mod message in the switch
+
+    @param parent Must implement controller, assertEqual, assertTrue
+    @param request The request, all set to go
+    @param clear_table If true, clear the flow table before installing
+    """
+
+    clear_table = test_param_get('clear_table', default=True)
+    if(clear_table_override != None):
+        clear_table = clear_table_override
+
+    if clear_table:
+        logging.debug("Clear flow table")
+        delete_all_flows(parent.controller)
+
+    logging.debug("Insert flow")
+    parent.controller.message_send(request)
+
+    do_barrier(parent.controller)
+
+def flow_match_test_port_pair(parent, ing_port, egr_ports, wildcards=None,
+                              vlan_vid=-1, pkt=None, exp_pkt=None,
+                              action_list=None):
+    """
+    Flow match test on single TCP packet
+    @param egr_ports A single port or list of ports
+
+    Run test with packet through switch from ing_port to egr_port
+    See flow_match_test for parameter descriptions
+    """
+
+    if wildcards is None:
+        wildcards = required_wildcards(parent)
+    logging.info("Pkt match test: " + str(ing_port) + " to " +
+                       str(egr_ports))
+    logging.debug("  WC: " + hex(wildcards) + " vlan: " + str(vlan_vid))
+    if pkt is None:
+        pkt = simple_tcp_packet(dl_vlan_enable=(vlan_vid >= 0), vlan_vid=vlan_vid)
+    if exp_pkt is None:
+        exp_pkt = pkt
+
+    request = flow_msg_create(parent, pkt, ing_port=ing_port,
+                              wildcards=wildcards, egr_ports=egr_ports,
+                              action_list=action_list)
+
+    flow_msg_install(parent, request)
+
+    logging.debug("Send packet: " + str(ing_port) + " to " +
+                        str(egr_ports))
+    parent.dataplane.send(ing_port, str(pkt))
+
+    exp_ports = [ing_port if port == ofp.OFPP_IN_PORT else port for port in egr_ports]
+    verify_packets(parent, exp_pkt, exp_ports)
+
+def flow_match_test_pktout(parent, ing_port, egr_ports,
+                           vlan_vid=-1, pkt=None, exp_pkt=None,
+                           action_list=None):
+    """
+    Packet-out test on single TCP packet
+    @param egr_ports A single port or list of ports
+
+    Run test sending packet-out to egr_ports. The goal is to test the actions
+    taken on the packet, not the matching which is of course irrelevant.
+    See flow_match_test for parameter descriptions
+    """
+
+    if pkt is None:
+        pkt = simple_tcp_packet(dl_vlan_enable=(vlan_vid >= 0), vlan_vid=vlan_vid)
+    if exp_pkt is None:
+        exp_pkt = pkt
+
+    msg = ofp.message.packet_out()
+    msg.in_port = ing_port
+    msg.buffer_id = 0xffffffff
+    msg.data = str(pkt)
+    if action_list is not None:
+        for act in action_list:
+            msg.actions.append(act)
+
+    # Set up output action
+    if egr_ports is not None:
+        for egr_port in egr_ports:
+            act = ofp.action.output()
+            act.port = egr_port
+            msg.actions.append(act)
+
+    logging.debug(msg.show())
+    parent.controller.message_send(msg)
+
+    exp_ports = [ing_port if port == ofp.OFPP_IN_PORT else port for port in egr_ports]
+    verify_packets(parent, exp_pkt, exp_ports)
+
+def get_egr_list(parent, of_ports, how_many, exclude_list=[]):
+    """
+    Generate a list of ports avoiding those in the exclude list
+    @param parent Supplies logging
+    @param of_ports List of OF port numbers
+    @param how_many Number of ports to be added to the list
+    @param exclude_list List of ports not to be used
+    @returns An empty list if unable to find enough ports
+    """
+
+    if how_many == 0:
+        return []
+
+    count = 0
+    egr_ports = []
+    for egr_idx in range(len(of_ports)):
+        if of_ports[egr_idx] not in exclude_list:
+            egr_ports.append(of_ports[egr_idx])
+            count += 1
+            if count >= how_many:
+                return egr_ports
+    logging.debug("Could not generate enough egress ports for test")
+    return []
+
+def flow_match_test(parent, port_map, wildcards=None, vlan_vid=-1, pkt=None,
+                    exp_pkt=None, action_list=None,
+                    max_test=0, egr_count=1, ing_port=False):
+    """
+    Run flow_match_test_port_pair on all port pairs and packet-out
+
+    @param max_test If > 0 no more than this number of tests are executed.
+    @param parent Must implement controller, dataplane, assertTrue, assertEqual
+    and logging
+    @param pkt If not None, use this packet for ingress
+    @param wildcards For flow match entry
+    @param vlan_vid If not -1, and pkt is None, create a pkt w/ VLAN tag
+    @param exp_pkt If not None, use this as the expected output pkt; els use pkt
+    @param action_list Additional actions to add to flow mod
+    @param egr_count Number of egress ports; -1 means get from config w/ dflt 2
+    """
+    if wildcards is None:
+        wildcards = required_wildcards(parent)
+    of_ports = port_map.keys()
+    of_ports.sort()
+    parent.assertTrue(len(of_ports) > 1, "Not enough ports for test")
+    test_count = 0
+
+    if egr_count == -1:
+        egr_count = test_param_get('egr_count', default=2)
+
+    for ing_idx in range(len(of_ports)):
+        ingress_port = of_ports[ing_idx]
+        egr_ports = get_egr_list(parent, of_ports, egr_count,
+                                 exclude_list=[ingress_port])
+        if ing_port:
+            egr_ports.append(ofp.OFPP_IN_PORT)
+        if len(egr_ports) == 0:
+            parent.assertTrue(0, "Failed to generate egress port list")
+
+        flow_match_test_port_pair(parent, ingress_port, egr_ports,
+                                  wildcards=wildcards, vlan_vid=vlan_vid,
+                                  pkt=pkt, exp_pkt=exp_pkt,
+                                  action_list=action_list)
+        test_count += 1
+        if (max_test > 0) and (test_count > max_test):
+            logging.info("Ran " + str(test_count) + " tests; exiting")
+            break
+
+    if not test_param_get('pktout_actions', default=True):
+        return
+
+    ingress_port = of_ports[0]
+    egr_ports = get_egr_list(parent, of_ports, egr_count,
+                             exclude_list=[ingress_port])
+    if ing_port:
+        egr_ports.append(ofp.OFPP_IN_PORT)
+    flow_match_test_pktout(parent, ingress_port, egr_ports,
+                           vlan_vid=vlan_vid,
+                           pkt=pkt, exp_pkt=exp_pkt,
+                           action_list=action_list)
+
+def test_param_get(key, default=None):
+    """
+    Return value passed via test-params if present
+
+    @param key The lookup key
+    @param default Default value to use if not found
+
+    If the pair 'key=val' appeared in the string passed to --test-params
+    on the command line, return val (as interpreted by exec).  Otherwise
+    return default value.
+
+    WARNING: TEST PARAMETERS MUST BE PYTHON IDENTIFIERS;
+    eg egr_count, not egr-count.
+    """
+    try:
+        exec oftest.config["test_params"]
+    except:
+        return default
+
+    try:
+        return eval(str(key))
+    except:
+        return default
+
+def action_generate(parent, field_to_mod, mod_field_vals):
+    """
+    Create an action to modify the field indicated in field_to_mod
+
+    @param parent Must implement, assertTrue
+    @param field_to_mod The field to modify as a string name
+    @param mod_field_vals Hash of values to use for modified values
+    """
+
+    act = None
+
+    if field_to_mod in ['pktlen']:
+        return None
+
+    if field_to_mod == 'eth_dst':
+        act = ofp.action.set_dl_dst()
+        act.dl_addr = oftest.parse.parse_mac(mod_field_vals['eth_dst'])
+    elif field_to_mod == 'eth_src':
+        act = ofp.action.set_dl_src()
+        act.dl_addr = oftest.parse.parse_mac(mod_field_vals['eth_src'])
+    elif field_to_mod == 'dl_vlan_enable':
+        if not mod_field_vals['dl_vlan_enable']: # Strip VLAN tag
+            act = ofp.action.strip_vlan()
+        # Add VLAN tag is handled by vlan_vid field
+        # Will return None in this case
+    elif field_to_mod == 'vlan_vid':
+        act = ofp.action.set_vlan_vid()
+        act.vlan_vid = mod_field_vals['vlan_vid']
+    elif field_to_mod == 'vlan_pcp':
+        act = ofp.action.set_vlan_pcp()
+        act.vlan_pcp = mod_field_vals['vlan_pcp']
+    elif field_to_mod == 'ip_src':
+        act = ofp.action.set_nw_src()
+        act.nw_addr = oftest.parse.parse_ip(mod_field_vals['ip_src'])
+    elif field_to_mod == 'ip_dst':
+        act = ofp.action.set_nw_dst()
+        act.nw_addr = oftest.parse.parse_ip(mod_field_vals['ip_dst'])
+    elif field_to_mod == 'ip_tos':
+        act = ofp.action.set_nw_tos()
+        act.nw_tos = mod_field_vals['ip_tos']
+    elif field_to_mod == 'tcp_sport':
+        act = ofp.action.set_tp_src()
+        act.tp_port = mod_field_vals['tcp_sport']
+    elif field_to_mod == 'tcp_dport':
+        act = ofp.action.set_tp_dst()
+        act.tp_port = mod_field_vals['tcp_dport']
+    elif field_to_mod == 'udp_sport':
+        act = ofp.action.set_tp_src()
+        act.tp_port = mod_field_vals['udp_sport']
+    elif field_to_mod == 'udp_dport':
+        act = ofp.action.set_tp_dst()
+        act.tp_port = mod_field_vals['udp_dport']
+    else:
+        parent.assertTrue(0, "Unknown field to modify: " + str(field_to_mod))
+
+    return act
+
+def pkt_action_setup(parent, start_field_vals={}, mod_field_vals={},
+                     mod_fields=[], tp="tcp", check_test_params=False):
+    """
+    Set up the ingress and expected packet and action list for a test
+
+    @param parent Must implement assertTrue
+    @param start_field_values Field values to use for ingress packet (optional)
+    @param mod_field_values Field values to use for modified packet (optional)
+    @param mod_fields The list of fields to be modified by the switch in the test.
+    @params check_test_params If True, will check the parameters vid, add_vlan
+    and strip_vlan from the command line.
+
+    Returns a triple:  pkt-to-send, expected-pkt, action-list
+    """
+
+    new_actions = []
+
+    base_pkt_params = {}
+    base_pkt_params['pktlen'] = 100
+    base_pkt_params['eth_dst'] = '00:DE:F0:12:34:56'
+    base_pkt_params['eth_src'] = '00:23:45:67:89:AB'
+    base_pkt_params['dl_vlan_enable'] = False
+    base_pkt_params['vlan_vid'] = 2
+    base_pkt_params['vlan_pcp'] = 0
+    base_pkt_params['ip_src'] = '192.168.0.1'
+    base_pkt_params['ip_dst'] = '192.168.0.2'
+    base_pkt_params['ip_tos'] = 0
+    if tp == "tcp":
+        base_pkt_params['tcp_sport'] = 1234
+        base_pkt_params['tcp_dport'] = 80
+    elif tp == "udp":
+        base_pkt_params['udp_sport'] = 1234
+        base_pkt_params['udp_dport'] = 80
+    for keyname in start_field_vals.keys():
+        base_pkt_params[keyname] = start_field_vals[keyname]
+
+    mod_pkt_params = {}
+    mod_pkt_params['pktlen'] = 100
+    mod_pkt_params['eth_dst'] = '00:21:0F:ED:CB:A9'
+    mod_pkt_params['eth_src'] = '00:ED:CB:A9:87:65'
+    mod_pkt_params['dl_vlan_enable'] = False
+    mod_pkt_params['vlan_vid'] = 3
+    mod_pkt_params['vlan_pcp'] = 7
+    mod_pkt_params['ip_src'] = '10.20.30.40'
+    mod_pkt_params['ip_dst'] = '50.60.70.80'
+    mod_pkt_params['ip_tos'] = 0xf0
+    if tp == "tcp":
+        mod_pkt_params['tcp_sport'] = 4321
+        mod_pkt_params['tcp_dport'] = 8765
+    elif tp == "udp":
+        mod_pkt_params['udp_sport'] = 4321
+        mod_pkt_params['udp_dport'] = 8765
+    for keyname in mod_field_vals.keys():
+        mod_pkt_params[keyname] = mod_field_vals[keyname]
+
+    # Check for test param modifications
+    strip = False
+    if check_test_params:
+        add_vlan = test_param_get('add_vlan')
+        strip_vlan = test_param_get('strip_vlan')
+        vid = test_param_get('vid')
+
+        if add_vlan and strip_vlan:
+            parent.assertTrue(0, "Add and strip VLAN both specified")
+
+        if vid:
+            base_pkt_params['dl_vlan_enable'] = True
+            base_pkt_params['vlan_vid'] = vid
+            if 'vlan_vid' in mod_fields:
+                mod_pkt_params['vlan_vid'] = vid + 1
+
+        if add_vlan:
+            base_pkt_params['dl_vlan_enable'] = False
+            mod_pkt_params['dl_vlan_enable'] = True
+            mod_pkt_params['pktlen'] = base_pkt_params['pktlen'] + 4
+            mod_fields.append('pktlen')
+            mod_fields.append('dl_vlan_enable')
+            if 'vlan_vid' not in mod_fields:
+                mod_fields.append('vlan_vid')
+        elif strip_vlan:
+            base_pkt_params['dl_vlan_enable'] = True
+            mod_pkt_params['dl_vlan_enable'] = False
+            mod_pkt_params['pktlen'] = base_pkt_params['pktlen'] - 4
+            mod_fields.append('dl_vlan_enable')
+            mod_fields.append('pktlen')
+
+    if tp == "tcp":
+        packet_builder = simple_tcp_packet
+    elif tp == "udp":
+        packet_builder = simple_udp_packet
+    else:
+        raise NotImplementedError("unknown transport protocol %s" % tp)
+
+    # Build the ingress packet
+    ingress_pkt = packet_builder(**base_pkt_params)
+
+    # Build the expected packet, modifying the indicated fields
+    for item in mod_fields:
+        base_pkt_params[item] = mod_pkt_params[item]
+        act = action_generate(parent, item, mod_pkt_params)
+        if act:
+            new_actions.append(act)
+
+    expected_pkt = packet_builder(**base_pkt_params)
+
+    return (ingress_pkt, expected_pkt, new_actions)
+
+# Generate a simple "drop" flow mod
+# If in_band is true, then only drop from first test port
+def flow_mod_gen(port_map, in_band):
+    request = ofp.message.flow_add()
+    request.match.wildcards = ofp.OFPFW_ALL
+    if in_band:
+        request.match.wildcards = ofp.OFPFW_ALL - ofp.OFPFW_IN_PORT
+        for of_port, ifname in port_map.items(): # Grab first port
+            break
+        request.match.in_port = of_port
+    request.buffer_id = 0xffffffff
+    return request
+
+def skip_message_emit(parent, s):
+    """
+    Print out a 'skipped' message to stderr
+
+    @param s The string to print out to the log file
+    """
+    global skipped_test_count
+
+    skipped_test_count += 1
+    logging.info("Skipping: " + s)
+    if oftest.config["debug"] < logging.WARNING:
+        sys.stderr.write("(skipped) ")
+    else:
+        sys.stderr.write("(S)")
+
+
+def all_stats_get(parent):
+    """
+    Get the aggregate stats for all flows in the table
+    @param parent Test instance with controller connection and assert
+    @returns dict with keys flows, packets, bytes, active (flows),
+    lookups, matched
+    """
+    stat_req = ofp.message.aggregate_stats_request()
+    stat_req.match = ofp.match()
+    stat_req.match.wildcards = ofp.OFPFW_ALL
+    stat_req.table_id = 0xff
+    stat_req.out_port = ofp.OFPP_NONE
+
+    rv = {}
+
+    (reply, pkt) = parent.controller.transact(stat_req)
+    parent.assertTrue(len(reply.entries) == 1, "Did not receive flow stats reply")
+
+    for obj in reply.entries:
+        (rv["flows"], rv["packets"], rv["bytes"]) = (obj.flow_count,
+                                                  obj.packet_count, obj.byte_count)
+        break
+
+    request = ofp.message.table_stats_request()
+    (reply , pkt) = parent.controller.transact(request)
+
+
+    (rv["active"], rv["lookups"], rv["matched"]) = (0,0,0)
+    for obj in reply.entries:
+        rv["active"] += obj.active_count
+        rv["lookups"] += obj.lookup_count
+        rv["matched"] += obj.matched_count
+
+    return rv
+
+_import_blacklist.add('FILTER')
+FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.'
+                for x in range(256)])
+
+def hex_dump_buffer(src, length=16):
+    """
+    Convert src to a hex dump string and return the string
+    @param src The source buffer
+    @param length The number of bytes shown in each line
+    @returns A string showing the hex dump
+    """
+    result = ["\n"]
+    for i in xrange(0, len(src), length):
+       chars = src[i:i+length]
+       hex = ' '.join(["%02x" % ord(x) for x in chars])
+       printable = ''.join(["%s" % ((ord(x) <= 127 and
+                                     FILTER[ord(x)]) or '.') for x in chars])
+       result.append("%04x  %-*s  %s\n" % (i, length*3, hex, printable))
+    return ''.join(result)
+
+def format_packet(pkt):
+    return "Packet length %d \n%s" % (len(str(pkt)),
+                                      hex_dump_buffer(str(pkt)))
+
+def inspect_packet(pkt):
+    """
+    Wrapper around scapy's show() method.
+    @returns A string showing the dissected packet.
+    """
+    from cStringIO import StringIO
+    out = None
+    backup = sys.stdout
+    try:
+        tmp = StringIO()
+        sys.stdout = tmp
+        pkt.show2()
+        out = tmp.getvalue()
+        tmp.close()
+    finally:
+        sys.stdout = backup
+    return out
+
+def nonstandard(cls):
+    """
+    Testcase decorator that marks the test as being non-standard.
+    These tests are not automatically added to the "standard" group.
+    """
+    cls._nonstandard = True
+    return cls
+
+def disabled(cls):
+    """
+    Testcase decorator that marks the test as being disabled.
+    These tests are not automatically added to the "standard" group or
+    their module's group.
+    """
+    cls._disabled = True
+    return cls
+
+def group(name):
+    """
+    Testcase decorator that adds the test to a group.
+    """
+    def fn(cls):
+        if not hasattr(cls, "_groups"):
+            cls._groups = []
+        cls._groups.append(name)
+        return cls
+    return fn
+
+def version(ver):
+    """
+    Testcase decorator that specifies which versions of OpenFlow the test
+    supports. The default is 1.0+. This decorator may only be used once.
+
+    Supported syntax:
+    1.0 -> 1.0
+    1.0,1.2,1.3 -> 1.0, 1.2, 1.3
+    1.0+ -> 1.0, 1.1, 1.2, 1.3
+    """
+    versions = parse_version(ver)
+    def fn(cls):
+        cls._versions = versions
+        return cls
+    return fn
+
+def parse_version(ver):
+    allowed_versions = ["1.0", "1.1", "1.2", "1.3"]
+    if re.match("^1\.\d+$", ver):
+        versions = set([ver])
+    elif re.match("^(1\.\d+)\+$", ver):
+        if not ver[:-1] in allowed_versions:
+            raise ValueError("invalid OpenFlow version %s" % ver[:-1])
+        versions = set()
+        if ver != "1.1+": versions.add("1.0")
+        if ver != "1.2+": versions.add("1.1")
+        if ver != "1.3+": versions.add("1.2")
+        versions.add("1.3")
+    else:
+        versions = set(ver.split(','))
+
+    for version in versions:
+        if not version in allowed_versions:
+            raise ValueError("invalid OpenFlow version %s" % version)
+
+    return versions
+
+assert(parse_version("1.0") == set(["1.0"]))
+assert(parse_version("1.0,1.2,1.3") == set(["1.0", "1.2", "1.3"]))
+assert(parse_version("1.0+") == set(["1.0", "1.1", "1.2", "1.3"]))
+
+def get_stats(test, req):
+    """
+    Retrieve a list of stats entries. Handles OFPSF_REPLY_MORE.
+    """
+    msgtype = ofp.OFPT_STATS_REPLY
+    more_flag = ofp.OFPSF_REPLY_MORE
+    stats = []
+    reply, _ = test.controller.transact(req)
+    test.assertTrue(reply is not None, "No response to stats request")
+    test.assertEquals(reply.type, msgtype, "Response had unexpected message type")
+    stats.extend(reply.entries)
+    while reply.flags & more_flag != 0:
+        reply, pkt = test.controller.poll(exp_msg=msgtype)
+        test.assertTrue(reply is not None, "No response to stats request")
+        stats.extend(reply.entries)
+    return stats
+
+def get_flow_stats(test, match, table_id=None,
+                   out_port=None, out_group=None,
+                   cookie=0, cookie_mask=0):
+    """
+    Retrieve a list of flow stats entries.
+    """
+
+    if table_id == None:
+        if ofp.OFP_VERSION <= 2:
+            table_id = 0xff
+        else:
+            table_id = ofp.OFPTT_ALL
+
+    if out_port == None:
+        if ofp.OFP_VERSION == 1:
+            out_port = ofp.OFPP_NONE
+        else:
+            out_port = ofp.OFPP_ANY
+
+    if out_group == None:
+        if ofp.OFP_VERSION > 1:
+            out_group = ofp.OFPP_ANY
+
+    req = ofp.message.flow_stats_request(match=match,
+                                         table_id=table_id,
+                                         out_port=out_port)
+    if ofp.OFP_VERSION > 1:
+        req.out_group = out_group
+        req.cookie = cookie
+        req.cookie_mask = cookie_mask
+
+    return get_stats(test, req)
+
+def get_port_stats(test, port_no):
+    """
+    Retrieve a list of port stats entries.
+    """
+    req = ofp.message.port_stats_request(port_no=port_no)
+    return get_stats(test, req)
+
+def get_queue_stats(test, port_no, queue_id):
+    """
+    Retrieve a list of queue stats entries.
+    """
+    req = ofp.message.queue_stats_request(port_no=port_no, queue_id=queue_id)
+    return get_stats(test, req)
+
+def verify_flow_stats(test, match, table_id=0xff,
+                      initial=[],
+                      pkts=None, bytes=None):
+    """
+    Verify that flow stats changed as expected.
+
+    Optionally takes an 'initial' list of stats entries, as returned by
+    get_flow_stats(). If 'initial' is not given the counters are assumed to
+    begin at 0.
+    """
+
+    def accumulate(stats):
+        pkts_acc = bytes_acc = 0
+        for stat in stats:
+            pkts_acc += stat.packet_count
+            bytes_acc += stat.byte_count
+        return (pkts_acc, bytes_acc)
+
+    pkts_before, bytes_before = accumulate(initial)
+
+    # Wait 10s for counters to update
+    pkt_diff = byte_diff = None
+    for i in range(0, 100):
+        stats = get_flow_stats(test, match, table_id=table_id)
+        pkts_after, bytes_after = accumulate(stats)
+        pkt_diff = pkts_after - pkts_before
+        byte_diff = bytes_after - bytes_before
+        if (pkts == None or pkt_diff >= pkts) and \
+           (bytes == None or byte_diff >= bytes):
+            break
+        time.sleep(0.1)
+
+    if pkts != None:
+        test.assertEquals(pkt_diff, pkts, "Flow packet counter not updated properly (expected increase of %d, got increase of %d)" % (pkts, pkt_diff))
+
+    if bytes != None:
+        test.assertTrue(byte_diff >= bytes and byte_diff <= bytes*1.1,
+                        "Flow byte counter not updated properly (expected increase of %d, got increase of %d)" % (bytes, byte_diff))
+
+def verify_port_stats(test, port,
+                      initial=[],
+                      tx_pkts=None, rx_pkts=None,
+                      tx_bytes=None, rx_bytes=None):
+    """
+    Verify that port stats changed as expected.
+
+    Optionally takes an 'initial' list of stats entries, as returned by
+    get_port_stats(). If 'initial' is not given the counters are assumed to
+    begin at 0.
+    """
+    def accumulate(stats):
+        tx_pkts_acc = rx_pkts_acc = tx_bytes_acc = rx_bytes_acc = 0
+        for stat in stats:
+            tx_pkts_acc += stat.tx_packets
+            rx_pkts_acc += stat.rx_packets
+            tx_bytes_acc += stat.tx_bytes
+            rx_bytes_acc += stat.rx_bytes
+        return (tx_pkts_acc, rx_pkts_acc, tx_bytes_acc, rx_bytes_acc)
+
+    tx_pkts_before, rx_pkts_before, \
+        tx_bytes_before, rx_bytes_before = accumulate(initial)
+
+    # Wait 10s for counters to update
+    for i in range(0, 100):
+        stats = get_port_stats(test, port)
+        tx_pkts_after, rx_pkts_after, \
+            tx_bytes_after, rx_bytes_after = accumulate(stats)
+        tx_pkts_diff = tx_pkts_after - tx_pkts_before
+        rx_pkts_diff = rx_pkts_after - rx_pkts_before
+        tx_bytes_diff = tx_bytes_after - tx_bytes_before
+        rx_bytes_diff = rx_bytes_after - rx_bytes_before
+        if (tx_pkts == None or tx_pkts <= tx_pkts_diff) and \
+           (rx_pkts == None or rx_pkts <= rx_pkts_diff) and \
+           (tx_bytes == None or tx_bytes <= tx_bytes_diff) and \
+           (rx_bytes == None or rx_bytes <= rx_bytes_diff):
+            break
+        time.sleep(0.1)
+
+    if (tx_pkts != None):
+        test.assertGreaterEqual(tx_pkts_diff, tx_pkts,
+            "Port TX packet counter is not updated correctly (expected increase of %d, got increase of %d)" % (tx_pkts, tx_pkts_diff))
+    if (rx_pkts != None):
+        test.assertGreaterEqual(rx_pkts_diff, rx_pkts,
+            "Port RX packet counter is not updated correctly (expected increase of %d, got increase of %d)" % (rx_pkts, rx_pkts_diff))
+    if (tx_bytes != None):
+        test.assertGreaterEqual(tx_bytes_diff, tx_bytes,
+            "Port TX byte counter is not updated correctly (expected increase of %d, got increase of %d)" % (tx_bytes, tx_bytes_diff))
+    if (rx_bytes != None):
+        test.assertGreaterEqual(rx_bytes_diff, rx_bytes,
+            "Port RX byte counter is not updated correctly (expected increase of %d, got increase of %d)" % (rx_bytes, rx_bytes_diff))
+
+def verify_queue_stats(test, port_no, queue_id,
+                       initial=[],
+                       pkts=None, bytes=None):
+    """
+    Verify that queue stats changed as expected.
+
+    Optionally takes an 'initial' list of stats entries, as returned by
+    get_queue_stats(). If 'initial' is not given the counters are assumed to
+    begin at 0.
+    """
+    def accumulate(stats):
+        pkts_acc = bytes_acc = 0
+        for stat in stats:
+            pkts_acc += stat.tx_packets
+            bytes_acc += stat.tx_bytes
+        return (pkts_acc, bytes_acc)
+
+    pkts_before, bytes_before = accumulate(initial)
+
+    # Wait 10s for counters to update
+    pkt_diff = byte_diff = None
+    for i in range(0, 100):
+        stats = get_queue_stats(test, port_no, queue_id)
+        pkts_after, bytes_after = accumulate(stats)
+        pkt_diff = pkts_after - pkts_before
+        byte_diff = bytes_after - bytes_before
+        if (pkts == None or pkt_diff >= pkts) and \
+           (bytes == None or byte_diff >= bytes):
+            break
+        time.sleep(0.1)
+
+    if pkts != None:
+        test.assertEquals(pkt_diff, pkts, "Queue packet counter not updated properly (expected increase of %d, got increase of %d)" % (pkts, pkt_diff))
+
+    if bytes != None:
+        test.assertTrue(byte_diff >= bytes and byte_diff <= bytes*1.1,
+                        "Queue byte counter not updated properly (expected increase of %d, got increase of %d)" % (bytes, byte_diff))
+
+def packet_in_match(msg, data, in_port=None, reason=None):
+    """
+    Check whether the packet_in message 'msg' has fields matching 'data',
+    'in_port', and 'reason'.
+
+    This function handles truncated packet_in data. The 'in_port' and 'reason'
+    parameters are optional.
+
+    @param msg packet_in message
+    @param data Expected packet_in data
+    @param in_port Expected packet_in in_port, or None
+    @param reason Expected packet_in reason, or None
+    """
+
+    if ofp.OFP_VERSION <= 2:
+        pkt_in_port = msg.in_port
+    else:
+        oxms = { type(oxm): oxm for oxm in msg.match.oxm_list }
+        if ofp.oxm.in_port in oxms:
+            pkt_in_port = oxms[ofp.oxm.in_port].value
+        else:
+            logging.warn("Missing in_port in packet-in message")
+            pkt_in_port = None
+
+    if in_port != None and in_port != pkt_in_port:
+        logging.debug("Incorrect packet_in in_port (expected %d, received %d)", in_port, pkt_in_port)
+        return False
+
+    if reason != None and reason != msg.reason:
+        logging.debug("Incorrect packet_in reason (expected %d, received %d)", reason, msg.reason)
+        return False
+
+    # Check that one of the packets is a prefix of the other.
+    # The received packet may be either truncated or padded, but not both.
+    # (Some of the padding may be truncated but that's irrelevant). We
+    # need to check that the smaller packet is a prefix of the larger one.
+    # Note that this check succeeds if the switch sends a zero-length
+    # packet-in.
+    compare_len = min(len(msg.data), len(data))
+    if data[:compare_len] != msg.data[:compare_len]:
+        logging.debug("Incorrect packet_in data")
+        logging.debug("Expected %s" % format_packet(data[:compare_len]))
+        logging.debug("Received %s" % format_packet(msg.data[:compare_len]))
+        return False
+
+    return True
+
+def verify_packet_in(test, data, in_port, reason, controller=None):
+    """
+    Assert that the controller receives a packet_in message matching data 'data'
+    from port 'in_port' with reason 'reason'. Does not trigger the packet_in
+    itself, that's up to the test case.
+
+    @param test Instance of base_tests.SimpleProtocol
+    @param pkt String to expect as the packet_in data
+    @param in_port OpenFlow port number to expect as the packet_in in_port
+    @param reason One of OFPR_* to expect as the packet_in reason
+    @param controller Controller instance, defaults to test.controller
+    @returns The received packet-in message
+    """
+
+    if controller == None:
+        controller = test.controller
+
+    end_time = time.time() + oftest.ofutils.default_timeout
+
+    while True:
+        msg, _ = controller.poll(ofp.OFPT_PACKET_IN, end_time - time.time())
+        if not msg:
+            # Timeout
+            break
+        elif packet_in_match(msg, data, in_port, reason):
+            # Found a matching message
+            break
+
+    test.assertTrue(msg is not None, 'Packet in message not received on port %r' % in_port)
+    return msg
+
+def verify_no_packet_in(test, data, in_port, controller=None):
+    """
+    Assert that the controller does not receive a packet_in message matching
+    data 'data' from port 'in_port'.
+
+    @param test Instance of base_tests.SimpleProtocol
+    @param pkt String to expect as the packet_in data
+    @param in_port OpenFlow port number to expect as the packet_in in_port
+    @param controller Controller instance, defaults to test.controller
+    """
+
+    if controller == None:
+        controller = test.controller
+
+    # Negative test, need to wait a short amount of time before checking we
+    # didn't receive the message.
+    time.sleep(oftest.ofutils.default_negative_timeout)
+
+    # Check every packet_in queued in the controller
+    while True:
+        msg, _ = controller.poll(ofp.OFPT_PACKET_IN, timeout=0)
+        if msg == None:
+            # No more queued packet_in messages
+            break
+        elif packet_in_match(msg, data, in_port, None):
+            # Found a matching message
+            break
+
+    if in_port == None:
+        test.assertTrue(msg == None, "Did not expect a packet-in message on any port")
+    else:
+        test.assertTrue(msg == None, "Did not expect a packet-in message on port %d" % in_port)
+
+def openflow_ports(num=None):
+    """
+    Return a list of 'num' OpenFlow port numbers
+
+    If 'num' is None, return all available ports. Otherwise, limit the length
+    of the result to 'num' and raise an exception if not enough ports are
+    available.
+    """
+    ports = sorted(oftest.config["port_map"].keys())
+    if num != None and len(ports) < num:
+        raise Exception("test requires %d ports but only %d are available" % (num, len(ports)))
+    return ports[:num]
+
+def verify_packet(test, pkt, ofport):
+    """
+    Check that an expected packet is received
+    """
+    logging.debug("Checking for pkt on port %r", ofport)
+    (rcv_port, rcv_pkt, pkt_time) = test.dataplane.poll(port_number=ofport, exp_pkt=str(pkt))
+    test.assertTrue(rcv_pkt != None, "Did not receive pkt on %r" % ofport)
+    return (rcv_port, rcv_pkt, pkt_time)
+
+def verify_no_packet(test, pkt, ofport):
+    """
+    Check that a particular packet is not received
+    """
+    logging.debug("Negative check for pkt on port %r", ofport)
+    (rcv_port, rcv_pkt, pkt_time) = \
+        test.dataplane.poll(
+            port_number=ofport, exp_pkt=str(pkt),
+            timeout=oftest.ofutils.default_negative_timeout)
+    test.assertTrue(rcv_pkt == None, "Received packet on %r" % ofport)
+
+def verify_no_other_packets(test):
+    """
+    Check that no unexpected packets are received
+
+    This is a no-op if the --relax option is in effect.
+    """
+    if oftest.config["relax"]:
+        return
+    logging.debug("Checking for unexpected packets on all ports")
+    (rcv_port, rcv_pkt, pkt_time) = test.dataplane.poll(timeout=oftest.ofutils.default_negative_timeout)
+    if rcv_pkt != None:
+        logging.debug("Received unexpected packet on port %r: %s", rcv_port, format_packet(rcv_pkt))
+    test.assertTrue(rcv_pkt == None, "Unexpected packet on port %r" % rcv_port)
+
+def verify_packets(test, pkt, ofports):
+    """
+    Check that a packet is received on certain ports
+
+    Also verifies that the packet is not received on any other ports, and that no
+    other packets are received (unless --relax is in effect).
+
+    This covers the common and simplest cases for checking dataplane outputs.
+    For more complex usage, like multiple different packets being output, or
+    multiple packets on the same port, use the primitive verify_packet,
+    verify_no_packet, and verify_no_other_packets functions directly.
+    """
+    pkt = str(pkt)
+    for ofport in openflow_ports():
+        if ofport in ofports:
+            verify_packet(test, pkt, ofport)
+        else:
+            verify_no_packet(test, pkt, ofport)
+    verify_no_other_packets(test)
+
+def verify_no_errors(ctrl):
+    error, _ = ctrl.poll(ofp.OFPT_ERROR, 0)
+    if error:
+        raise AssertionError("unexpected error type=%d code=%d" % (error.err_type, error.code))
+
+def verify_capability(test, capability):
+    """
+    Return True if DUT supports the specified capability.
+
+    @param test Instance of base_tests.SimpleProtocol
+    @param capability One of ofp_capabilities.
+    """
+    logging.info("Verifing that capability code is valid.")
+    test.assertIn(capability, ofp.const.ofp_capabilities_map,
+                  "Capability code %d does not exist." % capability)
+    capability_str = ofp.const.ofp_capabilities_map[capability]
+
+    logging.info(("Sending features_request to test if capability "
+                  "%s is supported."), capability_str)
+    req = ofp.message.features_request()
+    res, raw = test.controller.transact(req)
+    test.assertIsNotNone(res, "Did not receive a response from the DUT.")
+    test.assertEqual(res.type, ofp.OFPT_FEATURES_REPLY,
+                     ("Unexpected packet type %d received in response to "
+                      "OFPT_FEATURES_REQUEST") % res.type)
+    logging.info("Received features_reply.")
+
+    if (res.capabilities & capability) > 0:
+        logging.info("Switch capabilities bitmask claims to support %s",
+                     capability_str)
+        return True, res.capabilities
+    else:
+        logging.info("Capabilities bitmask does not support %s.",
+                     capability_str)
+        return False, res.capabilities
+
+def verify_configuration_flag(test, flag):
+    """
+    Return True if DUT supports specified configuration flag.
+
+    @param test Instance of base_tests.SimpleProtocol
+    @param flag One of ofp_config_flags.
+    @returns (supported, flags) Bool if flag is set and flag values.
+    """
+    logging.info("Verifing that flag is valid.")
+    test.assertIn(flag, ofp.const.ofp_config_flags_map,
+                  "flag  %s does not exist." % flag)
+    flag_str = ofp.const.ofp_config_flags_map[flag]
+
+    logging.info("Sending OFPT_GET_CONFIG_REQUEST.")
+    req = ofp.message.get_config_request()
+    rv = test.controller.message_send(req)
+    test.assertNotEqual(rv, -1, "Not able to send get_config_request.")
+
+    logging.info("Expecting OFPT_GET_CONFIG_REPLY ")
+    (res, pkt) = test.controller.poll(exp_msg=ofp.OFPT_GET_CONFIG_REPLY,
+                                      timeout=2)
+    test.assertIsNotNone(res, "Did not receive OFPT_GET_CONFIG_REPLY")
+    logging.info("Received OFPT_GET_CONFIG_REPLY ")
+
+    if res.flags == flag:
+        logging.info("%s flag is set.", flag_str)
+        return True, res.flags
+    else:
+        logging.info("%s flag is not set.", flag_str)
+        return False, res.flags
+
+__all__ = list(set(locals()) - _import_blacklist)
diff --git a/Fabric/Utilities/src/python/oftest/utils.py b/Fabric/Utilities/src/python/oftest/utils.py
new file mode 100644
index 0000000..ebc168d
--- /dev/null
+++ b/Fabric/Utilities/src/python/oftest/utils.py
@@ -0,0 +1,779 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+
+import Queue
+import itertools
+
+from oftest.testutils import *
+from accton_util import *
+
+"""
+MISC
+"""
+
+def print_port_stats(test, port):
+    entries = get_port_stats(test, port)
+    for item in entries:
+        packet_rcv          = item.rx_packets
+        packet_rcv_dropped  = item.rx_dropped
+        packet_rcv_errors   = item.rx_errors
+
+        packet_sent         = item.tx_packets
+        packet_sent_dropped = item.tx_dropped
+        packet_sent_errors  = item.tx_errors
+
+    print "\nPort %d stats count: tx %d rx %d - tx_dropped %d rx_dropped %d - tx_errors %d rx_errors %d" % (
+        port, packet_sent, packet_rcv, packet_sent_dropped, packet_rcv_dropped, packet_sent_errors, packet_rcv_errors
+        )
+
+def filter_dhcp(controller):
+    match = ofp.match( )
+    match.oxm_list.append( ofp.oxm.eth_type( 0x0800 ) )
+    match.oxm_list.append( ofp.oxm.ip_proto( 17 ) )
+    match.oxm_list.append( ofp.oxm.udp_src( 68 ) )
+    match.oxm_list.append( ofp.oxm.udp_dst( 67 ))
+    request = ofp.message.flow_add(
+        table_id=60,
+        cookie=42,
+        match=match,
+        instructions=[ofp.instruction.clear_actions( )],
+        buffer_id=ofp.OFP_NO_BUFFER,
+        priority=1
+        )
+    controller.message_send( request )
+    do_barrier( controller )
+
+def filter_ipv6(controller):
+    match = ofp.match( )
+    match.oxm_list.append( ofp.oxm.eth_type( 0x86dd ) )
+    request = ofp.message.flow_add(
+        table_id=60,
+        cookie=42,
+        match=match,
+        instructions=[ofp.instruction.clear_actions( )],
+        buffer_id=ofp.OFP_NO_BUFFER,
+        priority=1
+        )
+    controller.message_send( request )
+    do_barrier( controller )
+
+def filter_igmp(controller):
+    match = ofp.match( )
+    match.oxm_list.append( ofp.oxm.eth_type( 0x0800 ) )
+    match.oxm_list.append( ofp.oxm.ip_proto( 2 ) )
+    request = ofp.message.flow_add(
+        table_id=60,
+        cookie=42,
+        match=match,
+        instructions=[ofp.instruction.clear_actions( )],
+        buffer_id=ofp.OFP_NO_BUFFER,
+        priority=1
+        )
+    controller.message_send( request )
+    do_barrier( controller )
+
+"""
+MULTICAST Pipelines
+"""
+
+def fill_mcast_pipeline_L3toL2(
+    controller,
+    logging,
+    ports,
+    is_ingress_tagged,
+    is_egress_tagged,
+    is_vlan_translated,
+    is_max_vlan
+    ):
+    """
+    This method, according to the scenario, fills properly
+    the pipeline. The method generates using ports data the
+    necessary information to fill the multicast pipeline and
+    fills properly the pipeline which consists in this scenario:
+
+    i) to create l2 interface groups;
+    ii) to create l3 multicast groups;
+    iii) to add multicast flows;
+    iv) to add termination; flows;
+    v) to add vlan flows
+
+    Scenarios:
+    1) ingress untagged, egress untagged
+    2) ingress untagged, egress tagged
+    3) ingress tagged, egress untagged
+    4) ingress tagged, egress tagged, no translation
+    5) ingress tagged, egress tagged, translation
+    """
+
+    MAX_INTERNAL_VLAN           = 4094
+    # Used for no translation
+    FIXED_VLAN                  = 300
+    Groups                      = Queue.LifoQueue( )
+    L2_Groups                   = []
+    port_to_in_vlan             = {}
+    port_to_out_vlan            = {}
+    port_to_src_mac             = {}
+    port_to_src_mac_str         = {}
+    port_to_dst_mac             = {}
+    port_to_dst_mac_str         = {}
+    port_to_src_ip              = {}
+    port_to_src_ip_str          = {}
+    src_ip_0                    = 0xc0a80100
+    src_ip_0_str                = "192.168.1.%s"
+    dst_ip                      = 0xe0000001
+    switch_mac                  = [ 0x01, 0x00, 0x5e, 0x00, 0x00, 0x00 ]
+
+    for port in ports:
+        in_vlan_id  = port + 1
+        out_vlan_id = MAX_INTERNAL_VLAN - port
+        if is_max_vlan and not is_vlan_translated:
+            in_vlan_id  = MAX_INTERNAL_VLAN
+            out_vlan_id = MAX_INTERNAL_VLAN
+        elif not is_max_vlan and not is_vlan_translated:
+            in_vlan_id  = FIXED_VLAN
+            out_vlan_id = FIXED_VLAN
+        src_mac                     = [ 0x00, 0x11, 0x11, 0x11, 0x11, port ]
+        src_mac_str                 = ':'.join( [ '%02X' % x for x in src_mac ] )
+        dst_mac                     = [ 0x01, 0x00, 0x5e, 0x01, 0x01, port ]
+        dst_mac_str                 = ':'.join( [ '%02X' % x for x in dst_mac ] )
+        src_ip                      = src_ip_0 + port
+        src_ip_str                  = src_ip_0_str % port
+        port_to_in_vlan[port]       = in_vlan_id
+        port_to_out_vlan[port]      = out_vlan_id
+        port_to_src_mac[port]       = src_mac
+        port_to_src_mac_str[port]   = src_mac_str
+        port_to_dst_mac[port]       = dst_mac
+        port_to_dst_mac_str[port]   = dst_mac_str
+        port_to_src_ip[port]        = src_ip
+        port_to_src_ip_str[port]    = src_ip_str
+
+    for in_port in ports:
+
+        L2_Groups = []
+        # add vlan flows table
+        add_one_vlan_table_flow( controller, in_port, 1, port_to_in_vlan[in_port], flag=VLAN_TABLE_FLAG_ONLY_TAG )
+        if not is_ingress_tagged:
+            add_one_vlan_table_flow( controller, in_port, 1, port_to_in_vlan[in_port], flag=VLAN_TABLE_FLAG_ONLY_UNTAG )
+        elif is_vlan_translated:
+            add_one_vlan_table_flow_translation( controller, in_port, port_to_in_vlan[in_port], port_to_out_vlan[in_port], flag=VLAN_TABLE_FLAG_ONLY_TAG)
+        # add termination flow
+        if not is_vlan_translated:
+            add_termination_flow( controller, in_port, 0x0800, switch_mac, port_to_in_vlan[in_port] )
+        else:
+            add_termination_flow( controller, in_port, 0x0800, switch_mac, port_to_out_vlan[in_port] )
+
+        for out_port in ports:
+            if out_port == in_port:
+                continue
+            # add l2 interface group, vlan_id equals for each port and must coincide with mcast_group vlan_id
+            if not is_vlan_translated:
+                l2gid, msg = add_one_l2_interface_group( controller, out_port, vlan_id=port_to_in_vlan[in_port],
+                is_tagged=is_egress_tagged, send_barrier=True )
+            else:
+                l2gid, msg = add_one_l2_interface_group( controller, out_port, vlan_id=port_to_out_vlan[in_port],
+                is_tagged=is_egress_tagged, send_barrier=True )
+            Groups._put( l2gid )
+            L2_Groups.append( l2gid )
+
+        # add l3 mcast group
+        if not is_vlan_translated:
+            mcat_group_msg = add_l3_mcast_group( controller, port_to_in_vlan[in_port], in_port, L2_Groups )
+        else:
+            mcat_group_msg = add_l3_mcast_group( controller, port_to_out_vlan[in_port], in_port, L2_Groups )
+        Groups._put( mcat_group_msg.group_id )
+        # add mcast routing flow
+        if not is_vlan_translated:
+            add_mcast4_routing_flow( controller, port_to_in_vlan[in_port], port_to_src_ip[in_port], 0, dst_ip, mcat_group_msg.group_id )
+        else:
+            add_mcast4_routing_flow( controller, port_to_out_vlan[in_port], port_to_src_ip[in_port], 0, dst_ip, mcat_group_msg.group_id )
+
+    return (
+        port_to_in_vlan,
+        port_to_out_vlan,
+        port_to_src_mac_str,
+        port_to_dst_mac_str,
+        port_to_src_ip,
+        port_to_src_ip_str,
+        Groups
+        )
+
+def fill_mcast_pipeline_L3toL3(
+    controller,
+    logging,
+    ports,
+    is_ingress_tagged,
+    is_egress_tagged,
+    is_vlan_translated,
+    is_max_vlan
+    ):
+    """
+    This method, according to the scenario, fills properly
+    the pipeline. The method generates using ports data the
+    necessary information to fill the multicast pipeline and
+    fills properly the pipeline which consists in this scenario:
+
+    i) to create l2 interface groups;
+    ii)to create l3 interface groups;
+    iii) to create l3 multicast groups;
+    iv) to add multicast flows;
+    v) to add termination; flows;
+    vi) to add vlan flows
+
+    Scenarios:
+    1) ingress tagged, egress tagged, translation
+    """
+
+    Groups                      = Queue.LifoQueue( )
+    MAX_INTERNAL_VLAN           = 4094
+    port_to_in_vlan             = {}
+    port_to_out_vlan            = {}
+    port_to_src_mac             = {}
+    port_to_src_mac_str         = {}
+    port_to_dst_mac             = {}
+    port_to_dst_mac_str         = {}
+    port_to_src_ip              = {}
+    port_to_src_ip_str          = {}
+    port_to_intf_src_mac        = {}
+    port_to_intf_src_mac_str    = {}
+    src_ip_0                    = 0xc0a80100
+    src_ip_0_str                = "192.168.1.%s"
+    dst_ip                      = 0xe0000001
+    switch_mac                  = [ 0x01, 0x00, 0x5e, 0x00, 0x00, 0x00 ]
+
+    for port in ports:
+        in_vlan_id                     = port + 1
+        out_vlan_id                    = MAX_INTERNAL_VLAN - port
+        src_mac                        = [ 0x00, 0x11, 0x11, 0x11, 0x11, port ]
+        src_mac_str                    = ':'.join( [ '%02X' % x for x in src_mac ] )
+        dst_mac                        = [ 0x01, 0x00, 0x5e, 0x01, 0x01, port ]
+        dst_mac_str                    = ':'.join( [ '%02X' % x for x in dst_mac ] )
+        src_ip                         = src_ip_0 + port
+        src_ip_str                     = src_ip_0_str % port
+        intf_src_mac                   = [ 0x00, 0x00, 0x00, 0xcc, 0xcc, port ]
+        intf_src_mac_str               = ':'.join( [ '%02X' % x for x in intf_src_mac ] )
+        port_to_in_vlan[port]          = in_vlan_id
+        port_to_out_vlan[port]         = out_vlan_id
+        port_to_src_mac[port]          = src_mac
+        port_to_src_mac_str[port]      = src_mac_str
+        port_to_dst_mac[port]          = dst_mac
+        port_to_dst_mac_str[port]      = dst_mac_str
+        port_to_src_ip[port]           = src_ip
+        port_to_src_ip_str[port]       = src_ip_str
+        port_to_intf_src_mac[port]     = intf_src_mac
+        port_to_intf_src_mac_str[port] = intf_src_mac_str
+
+    for port in ports:
+        L3_Groups = []
+        for other_port in ports:
+            # add l2 interface group
+            l2gid, msg = add_one_l2_interface_group( controller, other_port, vlan_id=port_to_out_vlan[other_port],
+            is_tagged=True, send_barrier=True )
+            Groups._put( l2gid )
+            # add l3 interface group
+            l3group_ucast_msg = add_l3_interface_group( controller, other_port, port_to_out_vlan[other_port], port_to_in_vlan[other_port],
+            port_to_intf_src_mac[other_port] )
+            L3_Groups.append(l3group_ucast_msg.group_id)
+            Groups._put( l3group_ucast_msg.group_id )
+
+        # add mcast group
+        mcat_group_msg = add_l3_mcast_group( controller, port_to_in_vlan[port], port_to_in_vlan[port], L3_Groups )
+        do_barrier(controller)
+        Groups._put( mcat_group_msg.group_id )
+        # add mcast flow
+        add_mcast4_routing_flow( controller, port_to_in_vlan[port], port_to_src_ip[port], 0, dst_ip, mcat_group_msg.group_id, True )
+        # add termination flow
+        add_termination_flow( controller, port, 0x0800, switch_mac, port_to_in_vlan[port] )
+        # add vlan flow table
+        add_one_vlan_table_flow( controller, port, 1, port_to_in_vlan[port], flag=VLAN_TABLE_FLAG_ONLY_TAG )
+
+    return (
+        port_to_in_vlan,
+        port_to_out_vlan,
+        port_to_src_mac_str,
+        port_to_dst_mac_str,
+        port_to_src_ip,
+        port_to_src_ip_str,
+        port_to_intf_src_mac_str,
+        Groups
+        )
+
+"""
+VPWS Pipeline
+"""
+
+OF_DPA_MPLS_L2_VPN_Label     = 1
+OF_DPA_MPLS_Tunnel_Label_1   = 3
+OF_DPA_MPLS_Tunnel_Label_2   = 4
+
+EGRESS_UNTAGGED     = 1
+EGRESS_TAGGED       = 2
+EGRESS_TAGGED_TRANS = 3
+
+
+def fill_pw_initiation_pipeline(
+    controller,
+    logging,
+    in_port,
+    out_port,
+    ingress_tags,
+    egress_tag,
+    mpls_labels
+    ):
+    """
+    This method, according to the scenario, fills properly
+    the pw pipeline. The method generates using ports data the
+    necessary information to fill the pw pipeline and
+    fills properly the pipeline which consists into:
+
+    """
+
+    Groups                  = Queue.LifoQueue( )
+    out_vlan                = 4094
+    port_to_in_vlan_1       = {}
+    port_to_in_vlan_2       = {}
+    port_to_in_vlan_3       = {}
+    port_to_src_mac         = {}
+    port_to_src_mac_str     = {}
+    port_to_dst_mac         = {}
+    port_to_dst_mac_str     = {}
+    port_to_mpls_label_1    = {}
+    port_to_mpls_label_2    = {}
+    port_to_mpls_label_pw   = {}
+    ports                   = [in_port, out_port]
+
+    for port in ports:
+        in_vlan_id_1                = port + 1
+        in_vlan_id_2                = port + 100
+        in_vlan_id_3                = port + 300
+        mpls_label_1                = port + 100
+        mpls_label_2                = port + 200
+        mpls_label_pw               = port + 300
+        port_to_in_vlan_1[port]     = in_vlan_id_1
+        port_to_in_vlan_2[port]     = in_vlan_id_2
+        port_to_in_vlan_3[port]     = in_vlan_id_3
+        src_mac                     = [ 0x00, 0x00, 0x00, 0x00, 0x11, port ]
+        src_mac_str                 = ':'.join( [ '%02X' % x for x in src_mac ] )
+        dst_mac                     = [ 0x00, 0x00, 0x00, 0x11, 0x11, port ]
+        dst_mac_str                 = ':'.join( [ '%02X' % x for x in dst_mac ] )
+        port_to_src_mac[port]       = src_mac
+        port_to_src_mac_str[port]   = src_mac_str
+        port_to_dst_mac[port]       = dst_mac
+        port_to_dst_mac_str[port]   = dst_mac_str
+        port_to_mpls_label_1[port]  = mpls_label_1
+        port_to_mpls_label_2[port]  = mpls_label_2
+        port_to_mpls_label_pw[port] = mpls_label_pw
+
+    # add l2 interface group, we have to pop the VLAN;
+    l2_intf_gid, l2_intf_msg = add_one_l2_interface_group(
+        ctrl=controller,
+        port=out_port,
+        vlan_id=out_vlan,
+        is_tagged=False,
+        send_barrier=False
+        )
+    Groups._put( l2_intf_gid )
+    # add MPLS interface group
+    mpls_intf_gid, mpls_intf_msg = add_mpls_intf_group(
+        ctrl=controller,
+        ref_gid=l2_intf_gid,
+        dst_mac=port_to_dst_mac[in_port],
+        src_mac=port_to_src_mac[out_port],
+        vid=out_vlan,
+        index=in_port
+        )
+    Groups._put( mpls_intf_gid )
+    mpls_gid = mpls_intf_gid
+    # add MPLS tunnel label groups, the number depends on the labels
+    if mpls_labels == 2:
+        mpls_tunnel_gid, mpls_tunnel_msg = add_mpls_tunnel_label_group(
+        ctrl=controller,
+        ref_gid=mpls_intf_gid,
+        subtype=OF_DPA_MPLS_Tunnel_Label_2,
+        index=in_port,
+        label=port_to_mpls_label_2[in_port]
+        )
+        Groups._put( mpls_tunnel_gid )
+        mpls_tunnel_gid, mpls_tunnel_msg = add_mpls_tunnel_label_group(
+            ctrl=controller,
+            ref_gid=mpls_tunnel_gid,
+            subtype=OF_DPA_MPLS_Tunnel_Label_1,
+            index=in_port,
+            label=port_to_mpls_label_1[in_port]
+            )
+        Groups._put( mpls_tunnel_gid )
+        mpls_gid = mpls_tunnel_gid
+    elif mpls_labels == 1:
+        mpls_tunnel_gid, mpls_tunnel_msg = add_mpls_tunnel_label_group(
+            ctrl=controller,
+            ref_gid=mpls_intf_gid,
+            subtype=OF_DPA_MPLS_Tunnel_Label_1,
+            index=in_port,
+            label=port_to_mpls_label_1[in_port]
+            )
+        Groups._put( mpls_tunnel_gid )
+        mpls_gid = mpls_tunnel_gid
+    # add MPLS L2 VPN group
+    mpls_l2_vpn_gid, mpls_l2_vpn_msg = add_mpls_label_group(
+        ctrl=controller,
+        subtype=OF_DPA_MPLS_L2_VPN_Label,
+        index=in_port,
+        ref_gid=mpls_gid,
+        push_l2_header=True,
+        push_vlan=True,
+        push_mpls_header=True,
+        push_cw=True,
+        set_mpls_label=port_to_mpls_label_pw[in_port],
+        set_bos=1,
+        cpy_ttl_outward=True
+    )
+    Groups._put( mpls_l2_vpn_gid )
+    # add MPLS L2 port flow
+    add_mpls_l2_port_flow(
+        ctrl=controller,
+        of_port=in_port,
+        mpls_l2_port=in_port,
+        tunnel_index=1,
+        ref_gid=mpls_l2_vpn_gid
+        )
+    # add VLAN flows table
+    if ingress_tags == 2:
+        if egress_tag == EGRESS_TAGGED:
+            add_one_vlan_1_table_flow_pw(
+                ctrl=controller,
+                of_port=in_port,
+                tunnel_index=1,
+                new_outer_vlan_id=-1,
+                outer_vlan_id=port_to_in_vlan_2[in_port],
+                inner_vlan_id=port_to_in_vlan_1[in_port],
+                )
+        elif egress_tag == EGRESS_TAGGED_TRANS:
+            add_one_vlan_1_table_flow_pw(
+                ctrl=controller,
+                of_port=in_port,
+                tunnel_index=1,
+                new_outer_vlan_id=port_to_in_vlan_3[in_port],
+                outer_vlan_id=port_to_in_vlan_2[in_port],
+                inner_vlan_id=port_to_in_vlan_1[in_port],
+                )
+        add_one_vlan_table_flow(
+            ctrl=controller,
+            of_port=in_port,
+            vlan_id=port_to_in_vlan_2[in_port],
+            flag=VLAN_TABLE_FLAG_ONLY_STACKED,
+            )
+    elif ingress_tags == 1:
+        if egress_tag == EGRESS_TAGGED:
+            add_one_vlan_table_flow_pw(
+                ctrl=controller,
+                of_port=in_port,
+                tunnel_index=1,
+                vlan_id=port_to_in_vlan_1[in_port],
+                flag=VLAN_TABLE_FLAG_ONLY_TAG,
+                )
+        elif egress_tag == EGRESS_TAGGED_TRANS:
+            add_one_vlan_table_flow_pw(
+                ctrl=controller,
+                of_port=in_port,
+                tunnel_index=1,
+                vlan_id=port_to_in_vlan_1[in_port],
+                new_vlan_id=port_to_in_vlan_3[in_port],
+                flag=VLAN_TABLE_FLAG_ONLY_TAG,
+                )
+    elif ingress_tags == 0:
+        filter_dhcp(controller)
+        filter_ipv6(controller)
+        filter_igmp(controller)
+        if egress_tag == EGRESS_UNTAGGED:
+            add_one_vlan_table_flow_pw(
+                ctrl=controller,
+                of_port=in_port,
+                tunnel_index=1,
+                flag=VLAN_TABLE_FLAG_ONLY_UNTAG,
+                )
+        elif egress_tag == EGRESS_TAGGED:
+            add_one_vlan_table_flow_pw(
+                ctrl=controller,
+                of_port=in_port,
+                tunnel_index=1,
+                vlan_id=port_to_in_vlan_1[in_port],
+                flag=VLAN_TABLE_FLAG_ONLY_UNTAG,
+                )
+
+    return (
+        port_to_mpls_label_2,
+        port_to_mpls_label_1,
+        port_to_mpls_label_pw,
+        port_to_in_vlan_3,
+        port_to_in_vlan_2,
+        port_to_in_vlan_1,
+        port_to_src_mac_str,
+        port_to_dst_mac_str,
+        Groups
+        )
+
+MPLS_FLOW_TABLE_0           = 23
+OF_DPA_MPLS_SWAP_Label      = 5
+
+def fill_pw_intermediate_transport_pipeline(
+    controller,
+    logging,
+    ports,
+    mpls_labels
+    ):
+    """
+    This method, according to the scenario, fills properly
+    the pw pipeline. The method generates using ports data the
+    necessary information to fill the pw pipeline and
+    fills properly the pipeline which consists into:
+
+    """
+
+    Groups                  = Queue.LifoQueue( )
+    out_vlan                = 4094
+    port_to_src_mac         = {}
+    port_to_src_mac_str     = {}
+    port_to_dst_mac         = {}
+    port_to_dst_mac_str     = {}
+    port_to_mpls_label_2    = {}
+    port_to_mpls_label_1    = {}
+    port_to_mpls_label_pw   = {}
+    port_to_switch_mac      = {}
+    port_to_switch_mac_str  = {}
+
+    for port in ports:
+        mpls_label_1                    = port + 10
+        mpls_label_2                    = port + 100
+        mpls_label_pw                   = port + 300
+        src_mac                         = [ 0x00, 0x00, 0x00, 0x00, 0x11, port ]
+        src_mac_str                     = ':'.join( [ '%02X' % x for x in src_mac ] )
+        dst_mac                         = [ 0x00, 0x00, 0x00, 0x11, 0x11, port ]
+        dst_mac_str                     = ':'.join( [ '%02X' % x for x in dst_mac ] )
+        switch_mac                      = [ 0x00, 0x00, 0x00, 0x00, 0x00, port ]
+        switch_mac_str                  = ':'.join( [ '%02X' % x for x in switch_mac ] )
+        port_to_src_mac[port]           = src_mac
+        port_to_src_mac_str[port]       = src_mac_str
+        port_to_dst_mac[port]           = dst_mac
+        port_to_dst_mac_str[port]       = dst_mac_str
+        port_to_mpls_label_1[port]      = mpls_label_1
+        port_to_mpls_label_2[port]      = mpls_label_2
+        port_to_mpls_label_pw[port]     = mpls_label_pw
+        port_to_switch_mac[port]        = switch_mac
+        port_to_switch_mac_str[port]    = switch_mac_str
+
+    for pair in itertools.product(ports, ports):
+        in_port     = pair[0]
+        out_port    = pair[1]
+        if out_port == in_port:
+            continue
+        # add l2 interface group, we have to pop the VLAN;
+        l2_intf_gid, l2_intf_msg = add_one_l2_interface_group(
+            ctrl=controller,
+            port=out_port,
+            vlan_id=out_vlan,
+            is_tagged=False,
+            send_barrier=False
+            )
+        Groups._put( l2_intf_gid )
+        # add MPLS interface group
+        mpls_intf_gid, mpls_intf_msg = add_mpls_intf_group(
+            ctrl=controller,
+            ref_gid=l2_intf_gid,
+            dst_mac=port_to_dst_mac[in_port],
+            src_mac=port_to_src_mac[out_port],
+            vid=out_vlan,
+            index=in_port
+            )
+        Groups._put( mpls_intf_gid )
+        # add MPLS flows
+        if mpls_labels >=2:
+            add_mpls_flow_pw(
+                ctrl=controller,
+                action_group_id=mpls_intf_gid,
+                label=port_to_mpls_label_2[in_port],
+                ethertype=0x8847,
+                tunnel_index=1,
+                bos=0
+                )
+        else:
+            mpls_tunnel_gid, mpls_tunnel_msg = add_mpls_tunnel_label_group(
+                ctrl=controller,
+                ref_gid=mpls_intf_gid,
+                subtype=OF_DPA_MPLS_Tunnel_Label_2,
+                index=in_port,
+                label=port_to_mpls_label_2[in_port]
+                )
+            Groups._put( mpls_tunnel_gid )
+            mpls_tunnel_gid, mpls_tunnel_msg = add_mpls_tunnel_label_group(
+                ctrl=controller,
+                ref_gid=mpls_tunnel_gid,
+                subtype=OF_DPA_MPLS_Tunnel_Label_1,
+                index=in_port,
+                label=port_to_mpls_label_1[in_port]
+                )
+            Groups._put( mpls_tunnel_gid )
+            mpls_swap_gid, mpls_tunnel_msg = add_mpls_swap_label_group(
+                ctrl=controller,
+                ref_gid=mpls_tunnel_gid,
+                subtype=OF_DPA_MPLS_SWAP_Label,
+                index=in_port,
+                label=port_to_mpls_label_pw[in_port]
+                )
+            Groups._put( mpls_swap_gid )
+            add_mpls_flow_pw(
+                ctrl=controller,
+                action_group_id=mpls_swap_gid,
+                label=port_to_mpls_label_pw[in_port],
+                ethertype=0x8847,
+                tunnel_index=1,
+                bos=1,
+                popMPLS=False,
+                popL2=False
+                )
+        # add Termination flow
+        add_termination_flow(
+            ctrl=controller,
+            in_port=in_port,
+            eth_type=0x8847,
+            dst_mac=port_to_switch_mac[in_port],
+            vlanid=out_vlan,
+            goto_table=MPLS_FLOW_TABLE_0)
+        # add VLAN flows
+        add_one_vlan_table_flow(
+            ctrl=controller,
+            of_port=in_port,
+            vlan_id=out_vlan,
+            flag=VLAN_TABLE_FLAG_ONLY_TAG,
+            )
+        add_one_vlan_table_flow(
+            ctrl=controller,
+            of_port=in_port,
+            vlan_id=out_vlan,
+            flag=VLAN_TABLE_FLAG_ONLY_UNTAG
+            )
+
+    return (
+        port_to_mpls_label_2,
+        port_to_mpls_label_1,
+        port_to_mpls_label_pw,
+        port_to_switch_mac_str,
+        port_to_src_mac_str,
+        port_to_dst_mac_str,
+        Groups
+        )
+
+def fill_pw_termination_pipeline(
+    controller,
+    logging,
+    in_port,
+    out_port,
+    egress_tags
+    ):
+    """
+    This method, according to the scenario, fills properly
+    the pw pipeline. The method generates using ports data the
+    necessary information to fill the pw pipeline and
+    fills properly the pipeline which consists into:
+
+    """
+
+    Groups                  = Queue.LifoQueue( )
+    out_vlan                = 4094
+    port_to_mpls_label_pw   = {}
+    port_to_vlan_2          = {}
+    port_to_vlan_1          = {}
+    port_to_switch_mac      = {}
+    port_to_switch_mac_str  = {}
+    ports                   = [in_port, out_port]
+
+    for port in ports:
+        mpls_label_pw                   = port + 300
+        in_vlan_id_1                    = port + 1
+        in_vlan_id_2                    = port + 100
+        switch_mac                      = [ 0x00, 0x00, 0x00, 0x00, 0x11, port ]
+        switch_mac_str                  = ':'.join( [ '%02X' % x for x in switch_mac ] )
+        port_to_mpls_label_pw[port]     = mpls_label_pw
+        port_to_vlan_2[port]            = in_vlan_id_2
+        port_to_vlan_1[port]            = in_vlan_id_1
+        port_to_switch_mac[port]        = switch_mac
+        port_to_switch_mac_str[port]    = switch_mac_str
+
+    # add l2 interface group;
+    if egress_tags == 2:
+        l2_intf_gid, l2_intf_msg = add_one_l2_interface_group(
+            ctrl=controller,
+            port=out_port,
+            vlan_id=port_to_vlan_2[out_port],
+            is_tagged=True,
+            send_barrier=False
+            )
+    elif egress_tags == 1:
+        l2_intf_gid, l2_intf_msg = add_one_l2_interface_group(
+            ctrl=controller,
+            port=out_port,
+            vlan_id=port_to_vlan_1[out_port],
+            is_tagged=True,
+            send_barrier=False
+            )
+    elif egress_tags == 0:
+        l2_intf_gid, l2_intf_msg = add_one_l2_interface_group(
+            ctrl=controller,
+            port=out_port,
+            vlan_id=port_to_vlan_1[out_port],
+            is_tagged=False,
+            send_barrier=False
+            )
+
+    Groups._put( l2_intf_gid )
+    add_mpls_flow_pw(
+        ctrl=controller,
+        action_group_id=l2_intf_gid,
+        label=port_to_mpls_label_pw[out_port],
+        ethertype=0x6558,
+        bos=1,
+        tunnel_index=1,
+        popMPLS=True,
+        popL2=True,
+        of_port=in_port
+        )
+    # add Termination flow
+    add_termination_flow(
+        ctrl=controller,
+        in_port=in_port,
+        eth_type=0x8847,
+        dst_mac=port_to_switch_mac[in_port],
+        vlanid=out_vlan,
+        goto_table=MPLS_FLOW_TABLE_0)
+    # add VLAN flows
+    add_one_vlan_table_flow(
+        ctrl=controller,
+        of_port=in_port,
+        vlan_id=out_vlan,
+        flag=VLAN_TABLE_FLAG_ONLY_TAG,
+        )
+    add_one_vlan_table_flow(
+        ctrl=controller,
+        of_port=in_port,
+        vlan_id=out_vlan,
+        flag=VLAN_TABLE_FLAG_ONLY_UNTAG
+        )
+
+    return (
+        port_to_mpls_label_pw,
+        port_to_vlan_2,
+        port_to_vlan_1,
+        port_to_switch_mac_str,
+        Groups
+        )