Merge pull request #27 from InCNTRE/master

message_types 
diff --git a/oft b/oft
index 9e92a99..1f28784 100755
--- a/oft
+++ b/oft
@@ -153,11 +153,13 @@
     "param"              : None,
     "platform"           : "local",
     "platform_args"      : None,
-    "controller_host"    : "0.0.0.0",
+    "switch_ip"          : None,  # If not none, actively connect to switch
+    "controller_host"    : "0.0.0.0",  # For passive bind
     "controller_port"    : 6633,
     "relax"              : False,
     "test_spec"          : "all",
     "log_file"           : "oft.log",
+    "log_append"         : False,
     "list"               : False,
     "list_test_names"    : False, 
     "debug"              : _debug_default,
@@ -225,6 +227,8 @@
     parser.add_option("-P", "--platform", help=plat_help)
     parser.add_option("-H", "--host", dest="controller_host",
                       help="The IP/name of the test controller host")
+    parser.add_option("-S", "--switch-ip", dest="switch_ip",
+                      help="If set, actively connect to this switch by IP")
     parser.add_option("-p", "--port", dest="controller_port",
                       type="int", help="Port number of the test controller")
     test_list_help = """Indicate tests to run.  Valid entries are "all" (the
@@ -236,6 +240,8 @@
     parser.add_option("-T", "--test-spec", "--test-list", help=test_list_help)
     parser.add_option("--log-file", 
                       help="Name of log file, empty string to log to console")
+    parser.add_option("--log-append", action="store_true",
+                      help="Do not delete log file if specified")
     parser.add_option("--debug",
                       help="Debug lvl: debug, info, warning, error, critical")
     parser.add_option("--port-count", type="int",
@@ -312,7 +318,9 @@
     """
     _format = "%(asctime)s  %(name)-10s: %(levelname)-8s: %(message)s"
     _datefmt = "%H:%M:%S"
+    _mode = config["log_append"] and "a" or "w"
     logging.basicConfig(filename=config["log_file"],
+                        filemode=_mode,
                         level=config["dbg_level"],
                         format=_format, datefmt=_datefmt)
 
@@ -433,6 +441,9 @@
     if test.__name__ in profile_mod.skip_test_list:
         logging.info("Skipping test %s due to profile" % test.__name__)
         return TEST_PRIO_SKIP
+    if test.__name__ in profile_mod.run_test_list:
+        logging.info("Add test %s due to profile" % test.__name__)
+        return TEST_PRIO_DEFAULT
     return getattr(test, "priority", TEST_PRIO_DEFAULT)
 
 #
diff --git a/platforms/ovs-dummy.py b/platforms/ovs-dummy.py
new file mode 100644
index 0000000..2dad1a5
--- /dev/null
+++ b/platforms/ovs-dummy.py
@@ -0,0 +1,213 @@
+"""
+Dummy platform
+
+This platform uses Open vSwitch dummy interfaces.
+"""
+
+import logging
+import os
+import select
+import socket
+import struct
+import sys
+import time
+from threading import Thread
+from threading import Lock
+
+RCV_TIMEOUT = 10000
+RUN_DIR = os.environ.get("OVS_RUNDIR", "/var/run/openvswitch")
+
+class DataPlanePortOVSDummy(Thread):
+    """
+    Class defining a port monitoring object that uses Unix domain
+    sockets for ports, intended for connecting to Open vSwitch "dummy"
+    netdevs.
+    """
+
+    def __init__(self, interface_name, port_number, parent, max_pkts=1024):
+        """
+        Set up a port monitor object
+        @param interface_name The name of the physical interface like eth1
+        @param port_number The port number associated with this port
+        @param parent The controlling dataplane object; for pkt wait CV
+        @param max_pkts Maximum number of pkts to keep in queue
+        """
+        Thread.__init__(self)
+        self.interface_name = interface_name
+        self.max_pkts = max_pkts
+        self.packets_total = 0
+        self.packets = []
+        self.packets_discarded = 0
+        self.port_number = port_number
+        self.txq = []
+        self.txq_lock = Lock()
+        logname = "dp-" + interface_name
+        self.logger = logging.getLogger(logname)
+        try:
+            self.socket = DataPlanePortOVSDummy.interface_open(interface_name)
+        except:
+            self.logger.info("Could not open socket")
+            raise
+        self.logger.info("Opened port monitor (class %s)", type(self).__name__)
+        self.parent = parent
+
+    @staticmethod
+    def interface_open(interface_name):
+        """
+        Open a Unix domain socket interface.
+        @param interface_name port name as a string such as 'eth1'
+        @retval s socket
+        """
+        s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+        s.settimeout(RCV_TIMEOUT)
+        s.setblocking(0)
+        s.connect("%s/%s" % (RUN_DIR, interface_name))
+        return s
+
+    def run(self):
+        """
+        Activity function for class
+        """
+        self.running = True
+        rxbuf = ""
+        while self.running:
+            try:
+                self.txq_lock.acquire()
+                if self.txq:
+                    wlist = [self.socket]
+                else:
+                    wlist = []
+                self.txq_lock.release()
+
+                rout, wout, eout = select.select([self.socket], wlist, [], 1)
+            except:
+                print sys.exc_info()
+                self.logger.error("Select error, exiting")
+                break
+
+            if not self.running:
+                break
+
+            if wout:
+                self.txq_lock.acquire()
+                if self.txq:
+                    retval = self.socket.send(self.txq[0])
+                    if retval > 0:
+                        self.txq[0] = self.txq[0][retval:]
+                        if len(self.txq[0]) == 0:
+                            self.txq = self.txq[1:]
+                self.txq_lock.release()
+
+            if rout:
+                if len(rxbuf) < 2:
+                    n = 2 - len(rxbuf)
+                else:
+                    frame_len = struct.unpack('>h', rxbuf[:2])[0]
+                    n = (2 + frame_len) - len(rxbuf)
+
+                data = self.socket.recv(n)
+                rxbuf += data
+                if len(data) == n and len(rxbuf) > 2:
+                    rcvtime = time.time()
+                    self.logger.debug("Pkt len " + str(len(rxbuf)) +
+                             " in at " + str(rcvtime) + " on port " +
+                             str(self.port_number))
+
+                    # Enqueue packet
+                    with self.parent.pkt_sync:
+                        if len(self.packets) >= self.max_pkts:
+                            # Queue full, throw away oldest
+                            self.packets.pop(0)
+                            self.packets_discarded += 1
+                            self.logger.debug("Discarding oldest packet to make room")
+                        self.packets.append((rxbuf[2:], rcvtime))
+                        self.packets_total += 1
+                        self.parent.pkt_sync.notify_all()
+
+                    rxbuf = ''
+
+        self.logger.info("Thread exit")
+
+    def kill(self):
+        """
+        Terminate the running thread
+        """
+        self.logger.debug("Port monitor kill")
+        self.running = False
+        try:
+            self.socket.close()
+        except:
+            self.logger.info("Ignoring dataplane soc shutdown error")
+
+    def timestamp_head(self):
+        """
+        Return the timestamp of the head of queue or None if empty
+        """
+        rv = None
+        try:
+            rv = self.packets[0][1]
+        except:
+            rv = None
+        return rv
+
+    def flush(self):
+        """
+        Clear the packet queue
+        """
+        with self.parent.pkt_sync:
+            self.packets_discarded += len(self.packets)
+            self.packets = []
+
+    def send(self, packet):
+        """
+        Send a packet to the dataplane port
+        @param packet The packet data to send to the port
+        @retval The number of bytes sent
+        """
+
+        self.txq_lock.acquire()
+        if len(self.txq) < self.max_pkts:
+            self.txq.append(struct.pack('>h', len(packet)) + packet)
+            retval = len(packet)
+        else:
+            retval = 0
+        self.txq_lock.release()
+
+        return retval
+
+    def register(self, handler):
+        """
+        Register a callback function to receive packets from this
+        port.  The callback will be passed the packet, the
+        interface name and the port number (if set) on which the
+        packet was received.
+
+        To be implemented
+        """
+        pass
+
+    def show(self, prefix=''):
+        print prefix + "Name:          " + self.interface_name
+        print prefix + "Pkts pending:  " + str(len(self.packets))
+        print prefix + "Pkts total:    " + str(self.packets_total)
+        print prefix + "socket:        " + str(self.socket)
+
+# Update this dictionary to suit your environment.
+dummy_port_map = {
+    1 : "p1",
+    2 : "p2",
+    3 : "p3",
+    4 : "p4"
+}
+
+def platform_config_update(config):
+    """
+    Update configuration for the dummy platform
+
+    @param config The configuration dictionary to use/update
+    """
+    global dummy_port_map
+    config["port_map"] = dummy_port_map.copy()
+    config["caps_table_idx"] = 0
+    config["dataplane"] = {"portclass": DataPlanePortOVSDummy}
+    config["allow_user"] = True
diff --git a/profiles/default.py b/profiles/default.py
index 8313eb7..592797e 100644
--- a/profiles/default.py
+++ b/profiles/default.py
@@ -7,3 +7,7 @@
 #@var skip_test_list The list of tests to skip for this run
 skip_test_list = [
 ]
+
+#@var run_test_list List of tests to run which would normally be skipped
+run_test_list = [
+]
diff --git a/profiles/example.py b/profiles/example.py
index 6e23b92..0928119 100644
--- a/profiles/example.py
+++ b/profiles/example.py
@@ -19,15 +19,17 @@
 #@var skip_test_list The list of tests to skip for this run
 skip_test_list = []
 
-# TO BE IMPLEMENTED
-# A list of test cases with parameters(?)
-# TBD
+# A list of test cases
 #@var run_test_list List of tests to run which would normally be skipped
-run_test_list = dict(
-    # Example
-    # SomeTestCase = [dict(<params1>), dict(<params2>),...],
-)
+run_test_list = []
 
+#
+# Ideally, we should allow parameters to be specified
+#
+# run_test_list = (
+#     testcase = [dict(param1), dict(param2), ...],
+# )
+#
 # for test_dict in profile.run_test_list:
 #     for test_name, test_params in test_dict.items():
 #          ...
diff --git a/src/python/oftest/base_tests.py b/src/python/oftest/base_tests.py
index 3664d9f..734e213 100644
--- a/src/python/oftest/base_tests.py
+++ b/src/python/oftest/base_tests.py
@@ -25,6 +25,7 @@
     def setUp(self):
         logging.info("** START TEST CASE " + str(self))
         self.controller = controller.Controller(
+            switch=config["switch_ip"],
             host=config["controller_host"],
             port=config["controller_port"])
         # clean_shutdown should be set to False to force quit app
diff --git a/src/python/oftest/controller.py b/src/python/oftest/controller.py
index cfe2eda..b4c3ac5 100644
--- a/src/python/oftest/controller.py
+++ b/src/python/oftest/controller.py
@@ -86,6 +86,7 @@
     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
@@ -94,7 +95,7 @@
     @var dbg_state Debug indication of state
     """
 
-    def __init__(self, host='127.0.0.1', port=6633, max_pkts=1024):
+    def __init__(self, switch=None, host='127.0.0.1', port=6633, max_pkts=1024):
         Thread.__init__(self)
         # Socket related
         self.rcv_size = RCV_SIZE_DEFAULT
@@ -127,7 +128,8 @@
 
         # Settings
         self.max_pkts = max_pkts
-        self.passive = True
+        self.switch = switch
+        self.passive = not self.switch
         self.host = host
         self.port = port
         self.dbg_state = "init"
@@ -282,7 +284,7 @@
         @returns 0 on success, -1 on error
         """
 
-        if s and s == self.listen_socket:
+        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()
@@ -295,7 +297,7 @@
                 self.logger.warning("Error on listen socket accept")
                 return -1
             self.socs.append(sock)
-            self.logger.info("Incoming connection from %s" % str(addr))
+            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)
@@ -330,6 +332,23 @@
 
         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))
+            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 run(self):
         """
         Activity function for class
@@ -348,18 +367,19 @@
         self.dbg_state = "starting"
 
         # Create listen socket
-        self.logger.info("Create/listen at " + self.host + ":" + 
-                 str(self.port))
-        self.listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        self.listen_socket.setsockopt(socket.SOL_SOCKET, 
-                                      socket.SO_REUSEADDR, 1)
-        self.listen_socket.bind((self.host, self.port))
-        self.dbg_state = "listening"
-        self.listen_socket.listen(LISTEN_QUEUE_SIZE)
+        if self.passive:
+            self.logger.info("Create/listen at " + self.host + ":" + 
+                             str(self.port))
+            self.listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            self.listen_socket.setsockopt(socket.SOL_SOCKET, 
+                                          socket.SO_REUSEADDR, 1)
+            self.listen_socket.bind((self.host, self.port))
+            self.dbg_state = "listening"
+            self.listen_socket.listen(LISTEN_QUEUE_SIZE)
 
-        self.logger.info("Waiting for switch connection")
-        self.socs = [self.listen_socket]
-        self.dbg_state = "running"
+            self.logger.info("Listening for switch connection")
+            self.socs = [self.listen_socket]
+            self.dbg_state = "running"
 
         while self.active:
             try:
@@ -391,8 +411,28 @@
         @return Boolean, True if connected
         """
 
-        with self.connect_cv:
-            timed_wait(self.connect_cv, lambda: self.switch_socket, timeout=timeout)
+        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.socs = [soc]
+                self.dbg_state = "running"
+                self.switch_socket = soc
+                with self.connect_cv:
+                    if self.initial_hello:
+                        self.message_send(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:
+                timed_wait(self.connect_cv, lambda: self.switch_socket,
+                           timeout=timeout)
+
         return self.switch_socket is not None
         
     def disconnect(self, timeout=-1):
@@ -496,7 +536,7 @@
         If an error occurs, (None, None) is returned
         """
 
-        if exp_msg:
+        if exp_msg is not None:
             self.logger.debug("Poll for %s" % ofp_type_map[exp_msg])
         else:
             self.logger.debug("Poll for any OF message")
@@ -504,7 +544,7 @@
         # Take the packet from the queue
         def grab():
             if len(self.packets) > 0:
-                if not exp_msg:
+                if exp_msg is None:
                     self.logger.debug("Looking for any packet")
                     (msg, pkt) = self.packets.pop(0)
                     return (msg, pkt)
@@ -620,6 +660,7 @@
         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"
diff --git a/src/python/oftest/dataplane.py b/src/python/oftest/dataplane.py
index 2124421..717a85f 100644
--- a/src/python/oftest/dataplane.py
+++ b/src/python/oftest/dataplane.py
@@ -141,7 +141,7 @@
                 self.kill()
                 break
 
-            rcvtime = time.clock()
+            rcvtime = time.time()
             self.logger.debug("Pkt len " + str(len(rcvmsg)) +
                      " in at " + str(rcvtime) + " on port " +
                      str(self.port_number))
@@ -286,9 +286,8 @@
             # Enqueue packet
             with self.parent.pkt_sync:
                 for (timestamp, rcvmsg) in self.pcap.readpkts():
-                    rcvtime = time.clock()
                     self.logger.debug("Pkt len " + str(len(rcvmsg)) +
-                                      " in at " + str(rcvtime) + " on port " +
+                                      " in at " + str(timestamp) + " on port " +
                                       str(self.port_number))
 
                     if len(self.packets) >= self.max_pkts:
@@ -296,7 +295,7 @@
                         self.packets.pop(0)
                         self.packets_discarded += 1
                         self.logger.debug("Discarding oldest packet to make room")
-                    self.packets.append((rcvmsg, rcvtime))
+                    self.packets.append((rcvmsg, timestamp))
                     self.packets_total += 1
                 self.parent.pkt_sync.notify_all()
 
diff --git a/src/python/oftest/testutils.py b/src/python/oftest/testutils.py
index 53c18fe..50316b7 100644
--- a/src/python/oftest/testutils.py
+++ b/src/python/oftest/testutils.py
@@ -56,6 +56,8 @@
 def simple_tcp_packet(pktlen=100, 
                       dl_dst='00:01:02:03:04:05',
                       dl_src='00:06:07:08:09:0a',
+                      dl_qinq_enable=False,
+                      dl_vlan_outer=20,
                       dl_vlan_enable=False,
                       dl_vlan=0,
                       dl_vlan_pcp=0,
@@ -75,6 +77,8 @@
     @param len Length of packet in bytes w/o CRC
     @param dl_dst Destinatino MAC
     @param dl_src Source MAC
+    @param dl_qinq_enable True if the packet is double vlan tags
+    @param dl_vlan_outer Outer VLAN ID
     @param dl_vlan_enable True if the packet is with vlan, False otherwise
     @param dl_vlan VLAN ID
     @param dl_vlan_pcp VLAN priority
@@ -93,7 +97,13 @@
         pktlen = MINSIZE
 
     # Note Dot1Q.id is really CFI
-    if (dl_vlan_enable):
+    if (dl_qinq_enable):
+        pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
+            scapy.Dot1Q(prio=0, id=0, vlan=dl_vlan_outer)/ \
+            scapy.Dot1Q(prio=dl_vlan_pcp, id=dl_vlan_cfi, vlan=dl_vlan)/ \
+            scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ihl=ip_ihl)/ \
+            scapy.TCP(sport=tcp_sport, dport=tcp_dport)
+    elif (dl_vlan_enable):
         pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
             scapy.Dot1Q(prio=dl_vlan_pcp, id=dl_vlan_cfi, vlan=dl_vlan)/ \
             scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ihl=ip_ihl)/ \
@@ -109,9 +119,64 @@
                 scapy.TCP(sport=tcp_sport, dport=tcp_dport)
 
     pkt = pkt/("D" * (pktlen - len(pkt)))
-    
-    #print pkt.show()
-    #print scapy.Ether(str(pkt)).show()
+
+    return pkt
+
+def simple_udp_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='192.168.0.1',
+                      ip_dst='192.168.0.2',
+                      ip_tos=0,
+                      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 dl_dst Destination 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 IP source
+    @param ip_dst IP destination
+    @param ip_tos IP ToS
+    @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=dl_dst, src=dl_src)/ \
+            scapy.Dot1Q(prio=dl_vlan_pcp, id=dl_vlan_cfi, vlan=dl_vlan)/ \
+            scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ihl=ip_ihl)/ \
+            scapy.UDP(sport=udp_sport, dport=udp_dport)
+    else:
+        if not ip_options:
+            pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
+                scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ihl=ip_ihl)/ \
+                scapy.UDP(sport=udp_sport, dport=udp_dport)
+        else:
+            pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
+                scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ihl=ip_ihl, options=ip_options)/ \
+                scapy.UDP(sport=udp_sport, dport=udp_dport)
+
+    pkt = pkt/("D" * (pktlen - len(pkt)))
 
     return pkt
 
@@ -179,13 +244,13 @@
 
     return pkt
 
-def do_barrier(ctrl):
+def do_barrier(ctrl, timeout=-1):
     """
     Do a barrier command
     Return 0 on success, -1 on error
     """
     b = message.barrier_request()
-    (resp, pkt) = ctrl.transact(b)
+    (resp, pkt) = ctrl.transact(b, timeout=timeout)
     # We'll trust the transaction processing in the controller that xid matched
     if not resp:
         return -1
@@ -251,6 +316,12 @@
     @param no_ports Set or list of ports that should not receive packet
     @param assert_if Object that implements assertXXX
     """
+
+    # Wait this long for packets that we don't expect to receive.
+    # 100ms is (rarely) too short for positive tests on slow
+    # switches but is definitely not too short for a negative test.
+    negative_timeout = 0.1
+
     exp_pkt_arg = None
     if config["relax"]:
         exp_pkt_arg = pkt
@@ -268,11 +339,11 @@
                              "Response packet does not match send packet " +
                              "on port " + str(ofport))
     if len(no_ports) > 0:
-        time.sleep(1)
+        time.sleep(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=1, exp_pkt=exp_pkt_arg)
+            port_number=ofport, timeout=0, exp_pkt=exp_pkt_arg)
         assert_if.assertTrue(rcv_pkt is None, 
                              "Unexpected pkt on port " + str(ofport))
 
@@ -316,6 +387,8 @@
                                 + 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))
 
@@ -499,6 +572,43 @@
         exp_pkt = pkt
     receive_pkt_verify(parent, egr_ports, exp_pkt, ing_port)
 
+def flow_match_test_pktout(parent, ing_port, egr_ports,
+                           dl_vlan=-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=(dl_vlan >= 0), dl_vlan=dl_vlan)
+
+    msg = message.packet_out()
+    msg.in_port = ing_port
+    msg.data = str(pkt)
+    if action_list is not None:
+        for act in action_list:
+            assert(msg.actions.add(act))
+
+    # Set up output action
+    if egr_ports is not None:
+        for egr_port in egr_ports:
+            act = action.action_output()
+            act.port = egr_port
+            assert(msg.actions.add(act))
+
+    logging.debug(msg.show())
+    rv = parent.controller.message_send(msg)
+    parent.assertTrue(rv == 0, "Error sending out message")
+
+    if exp_pkt is None:
+        exp_pkt = pkt
+    receive_pkt_verify(parent, egr_ports, exp_pkt, ing_port)
+
 def get_egr_list(parent, of_ports, how_many, exclude_list=[]):
     """
     Generate a list of ports avoiding those in the exclude list
@@ -527,7 +637,7 @@
                     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
+    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
@@ -565,7 +675,18 @@
         test_count += 1
         if (max_test > 0) and (test_count > max_test):
             logging.info("Ran " + str(test_count) + " tests; exiting")
-            return
+            break
+
+
+    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,
+                           dl_vlan=dl_vlan,
+                           pkt=pkt, exp_pkt=exp_pkt,
+                           action_list=action_list)
 
 def test_param_get(key, default=None):
     """
@@ -639,13 +760,19 @@
     elif field_to_mod == 'tcp_dport':
         act = action.action_set_tp_dst()
         act.tp_port = mod_field_vals['tcp_dport']
+    elif field_to_mod == 'udp_sport':
+        act = action.action_set_tp_src()
+        act.tp_port = mod_field_vals['udp_sport']
+    elif field_to_mod == 'udp_dport':
+        act = action.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=[], check_test_params=False):
+                     mod_fields=[], tp="tcp", check_test_params=False):
     """
     Set up the ingress and expected packet and action list for a test
 
@@ -671,8 +798,12 @@
     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
+    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]
 
@@ -686,8 +817,12 @@
     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
+    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]
 
@@ -722,8 +857,15 @@
             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 = simple_tcp_packet(**base_pkt_params)
+    ingress_pkt = packet_builder(**base_pkt_params)
 
     # Build the expected packet, modifying the indicated fields
     for item in mod_fields:
@@ -732,7 +874,7 @@
         if act:
             new_actions.append(act)
 
-    expected_pkt = simple_tcp_packet(**base_pkt_params)
+    expected_pkt = packet_builder(**base_pkt_params)
 
     return (ingress_pkt, expected_pkt, new_actions)
 
@@ -823,3 +965,20 @@
 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:
+        sys.stdout = StringIO()
+        pkt.show2()
+        out = sys.stdout.getvalue()
+        sys.stdout.close()
+    finally:
+        sys.stdout = backup
+    return out
diff --git a/tests/FuncUtils.py b/tests/FuncUtils.py
index a252d37..f70fb7e 100644
--- a/tests/FuncUtils.py
+++ b/tests/FuncUtils.py
@@ -285,7 +285,7 @@
     #Generate Match_Vlan_Priority
 
     #Create a simple tcp packet and generate match on ethernet dst address flow
-    pkt_matchvlanpcp = simple_tcp_packet(dl_vlan_enable=True,dl_vlan=1,dl_vlan_pcp=10)
+    pkt_matchvlanpcp = simple_tcp_packet(dl_vlan_enable=True,dl_vlan=1,dl_vlan_pcp=5)
     match = parse.packet_to_flow_match(pkt_matchvlanpcp)
     self.assertTrue(match is not None, "Could not generate flow match from pkt")
 
@@ -367,7 +367,7 @@
     #Generate a Match on IP Type of service flow
 
         #Create a simple tcp packet and generate match on Type of service 
-    pkt_iptos = simple_tcp_packet(ip_tos=30)
+    pkt_iptos = simple_tcp_packet(ip_tos=28)
     match = parse.packet_to_flow_match(pkt_iptos)
     self.assertTrue(match is not None, "Could not generate flow match from pkt")
 
diff --git a/tests/basic.py b/tests/basic.py
index e4b933e..862b6df 100644
--- a/tests/basic.py
+++ b/tests/basic.py
@@ -208,6 +208,7 @@
 
                logging.info("PKT OUT test with %s, port %s" % (opt, dp_port))
                msg = message.packet_out()
+               msg.in_port = ofp.OFPP_NONE
                msg.data = str(outpkt)
                act = action.action_output()
                act.port = dp_port
@@ -264,6 +265,7 @@
                logging.info("PKT OUT test with " + opt +
                                  ", ports " + str(dp_ports))
                msg = message.packet_out()
+               msg.in_port = ofp.OFPP_NONE
                msg.data = str(outpkt)
                act = action.action_output()
                for i in range(0,num_ports):
diff --git a/tests/caps.py b/tests/caps.py
index fe90310..6c48840 100644
--- a/tests/caps.py
+++ b/tests/caps.py
@@ -60,7 +60,7 @@
     logging.info("Inserting initial flow")
     rv = obj.controller.message_send(request)
     obj.assertTrue(rv != -1, "Error installing flow mod")
-    obj.assertEqual(do_barrier(obj.controller), 0, "Barrier failed")
+    obj.assertEqual(do_barrier(obj.controller, timeout=10), 0, "Barrier failed")
     flow_count = 1
 
     logging.info("Table idx: " + str(table_idx))
@@ -71,7 +71,7 @@
         rv = obj.controller.message_send(request)
         flow_count += 1
         if flow_count % count_check == 0:
-            obj.assertEqual(do_barrier(obj.controller), 0, "Barrier failed")
+            obj.assertEqual(do_barrier(obj.controller, timeout=10), 0, "Barrier failed")
             response, pkt = obj.controller.transact(tstats)
             obj.assertTrue(response is not None, "Get tab stats failed")
             logging.info(response.show())
@@ -87,6 +87,11 @@
     logging.error("RESULT: " + str(flow_count) + " flows inserted")
     logging.error("RESULT: " + str(active_flows) + " flows reported")
 
+    # clean up and wait a bit in case the table is really big
+    rv = delete_all_flows(obj.controller)
+    time.sleep(flow_count / 100)
+
+
 
 class FillTableExact(base_tests.SimpleProtocol):
     """
diff --git a/tests/cxn.py b/tests/cxn.py
index 5524434..e9059cc 100644
--- a/tests/cxn.py
+++ b/tests/cxn.py
@@ -112,55 +112,84 @@
         self.num_controllers = test_param_get('num_controllers', default=1)
         self.controller_timeout = test_param_get('controller_timeout',
                                                  default=-1)
+        self.hello_timeout = test_param_get('hello_timeout',
+                                            default=5)
+        self.features_req_timeout = test_param_get('features_req_timeout',
+                                                   default=5)
 
         for i in range(self.num_controllers):
             self.controllerSetup(config["controller_host"],
                                  config["controller_port"]+i)
         for i in range(self.num_controllers):
-            self.controllers[i].handshake_done = False
+            self.controllers[i].cstate = 0
+            self.controllers[i].keep_alive = True
+        tick = 0.1  # time period in seconds at which controllers are handled
 
-        # try to maintain switch connections for specified timeout
-        # -1 means forever
         while True:
             for con in self.controllers:
-                if con.switch_socket and con.handshake_done:
-                    if (self.controller_timeout < 0 or
-                        con.count < self.controller_timeout):
-                        logging.info(con.host + ":" + str(con.port) + 
-                                     ": maintaining connection to " +
-                                     str(con.switch_addr))
-                        con.count = con.count + 1
-                    else:
-                        logging.info(con.host + ":" + str(con.port) + 
-                                     ": disconnecting from " +
-                                     str(con.switch_addr))
-                        con.disconnect()
-                        con.handshake_done = False
-                        con.count = 0
-                    time.sleep(1)
-                else:
-                    #@todo Add an option to wait for a pkt transaction to 
-                    # ensure version compatibilty?
-                    con.connect(self.default_timeout)
-                    if not con.switch_socket:
-                        logging.info("Did not connect to switch")
-                        continue
-                    logging.info("TCP Connected " + str(con.switch_addr))
-                    logging.info("Sending hello")
-                    con.message_send(message.hello())
-                    request = message.features_request()
-                    reply, pkt = con.transact(request, 
-                                              timeout=self.default_timeout)
-                    if reply:
-                        logging.info("Handshake complete with " + 
-                                    str(con.switch_addr))
-                        con.handshake_done = True
-                        con.keep_alive = True
-                        con.count = 0
-                    else:
-                        logging.info("Did not complete features_request " +
-                                     "for handshake")
-                        con.disconnect()
-                        con.handshake_done = False
-                        con.count = 0
+                condesc = con.host + ":" + str(con.port) + ": "
+                logging.debug("Checking " + condesc)
 
+                if con.switch_socket: 
+                    if con.cstate == 0:
+                        logging.info(condesc + "Sending hello to " +
+                                     str(con.switch_addr))
+                        con.message_send(message.hello())
+                        con.cstate = 1
+                        con.count = 0
+                    elif con.cstate == 1:
+                        reply, pkt = con.poll(exp_msg=ofp.OFPT_HELLO,
+                                              timeout=0)
+                        if reply is not None:
+                            logging.info(condesc + 
+                                         "Hello received from " +
+                                         str(con.switch_addr))
+                            con.cstate = 2
+                        else:
+                            con.count = con.count + 1
+                            # fall back to previous state on timeout
+                            if con.count >= self.hello_timeout/tick:
+                                logging.info(condesc + 
+                                             "Timeout hello from " +
+                                             str(con.switch_addr))
+                                con.cstate = 0
+                    elif con.cstate == 2:
+                        logging.info(condesc + "Sending features request to " +
+                                     str(con.switch_addr))
+                        con.message_send(message.features_request())
+                        con.cstate = 3
+                        con.count = 0
+                    elif con.cstate == 3:
+                        reply, pkt = con.poll(exp_msg=ofp.OFPT_FEATURES_REPLY,
+                                              timeout=0)
+                        if reply is not None:
+                            logging.info(condesc + 
+                                         "Features request received from " +
+                                         str(con.switch_addr))
+                            con.cstate = 4
+                            con.count = 0
+                        else:
+                            con.count = con.count + 1
+                            # fall back to previous state on timeout
+                            if con.count >= self.features_req_timeout/tick:
+                                logging.info(condesc +
+                                             "Timeout features request from " +
+                                             str(con.switch_addr))
+                                con.cstate = 2
+                    elif con.cstate == 4:
+                        if (self.controller_timeout < 0 or
+                            con.count < self.controller_timeout/tick):
+                            logging.debug(condesc +
+                                          "Maintaining connection to " +
+                                          str(con.switch_addr))
+                            con.count = con.count + 1
+                        else:
+                            logging.info(condesc + 
+                                         "Disconnecting from " +
+                                         str(con.switch_addr))
+                            con.disconnect()
+                            con.cstate = 0
+                else:
+                    con.cstate = 0
+
+            time.sleep(tick)
diff --git a/tests/flow_matches.py b/tests/flow_matches.py
index 9edd4c0..4b12a95 100644
--- a/tests/flow_matches.py
+++ b/tests/flow_matches.py
@@ -414,7 +414,7 @@
         receive_pkt_check(self.dataplane,pkt,[yes_ports],no_ports,self)
         
         #Create a non-matching packet , verify packet_in get generated
-        pkt2 = simple_tcp_packet(ip_tos=2);
+        pkt2 = simple_tcp_packet(ip_tos=4);
         self.dataplane.send(of_ports[0], str(pkt2))
         (response, raw) = self.controller.poll(ofp.OFPT_PACKET_IN,timeout=4)
         self.assertTrue(response is not None, "PacketIn not received for non matching packet")
diff --git a/tests/flow_stats.py b/tests/flow_stats.py
index 2a42b94..040fec8 100644
--- a/tests/flow_stats.py
+++ b/tests/flow_stats.py
@@ -458,3 +458,78 @@
         self.assertEquals(response.stats[0].flow_count, 0)
         self.assertEquals(response.stats[0].packet_count, 0)
         self.assertEquals(response.stats[0].byte_count, 0)
+
+class DeletedFlowStats(base_tests.SimpleDataPlane):
+    """
+    Verify flow stats are properly returned when a flow is deleted.
+
+    Generate a packet
+    Generate and install a matching flow
+    Send the packet
+    Delete the flow
+    Verify that the flow_removed message has the correct stats
+    """
+
+    def runTest(self):
+        # TODO: set from command-line parameter
+        test_timeout = 60
+
+        of_ports = config["port_map"].keys()
+        of_ports.sort()
+        self.assertTrue(len(of_ports) > 1, "Not enough ports for test")
+
+        rc = delete_all_flows(self.controller)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+
+        # build packet
+        pkt = simple_tcp_packet()
+        match = packet_to_flow_match(self, pkt)
+        match.wildcards &= ~ofp.OFPFW_IN_PORT
+        self.assertTrue(match is not None,
+                        "Could not generate flow match from pkt")
+        act = action.action_output()
+
+        # build flow
+        ingress_port = of_ports[0];
+        egress_port = of_ports[1];
+        logging.info("Ingress " + str(ingress_port) +
+                       " to egress " + str(egress_port))
+        match.in_port = ingress_port
+        flow_mod_msg = message.flow_mod()
+        flow_mod_msg.match = copy.deepcopy(match)
+        flow_mod_msg.cookie = random.randint(0,9007199254740992)
+        flow_mod_msg.buffer_id = 0xffffffff
+        flow_mod_msg.idle_timeout = 0
+        flow_mod_msg.hard_timeout = 0
+        flow_mod_msg.priority = 100
+        flow_mod_msg.flags = ofp.OFPFF_SEND_FLOW_REM
+        act.port = egress_port
+        self.assertTrue(flow_mod_msg.actions.add(act), "Could not add action")
+
+        # send flow
+        logging.info("Inserting flow")
+        rv = self.controller.message_send(flow_mod_msg)
+        self.assertTrue(rv != -1, "Error installing flow mod")
+        self.assertEqual(do_barrier(self.controller), 0, "Barrier failed")
+
+        # send packet N times
+        num_sends = random.randint(10,20)
+        logging.info("Sending " + str(num_sends) + " test packets")
+        for i in range(0,num_sends):
+            sendPacket(self, pkt, ingress_port, egress_port,
+                       test_timeout)
+
+        # delete flow
+        logging.info("Deleting flow")
+        rc = delete_all_flows(self.controller)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+
+        # wait for flow_removed message
+        flow_removed, _ = self.controller.poll(
+            exp_msg=ofp.OFPT_FLOW_REMOVED, timeout=test_timeout)
+
+        self.assertTrue(flow_removed != None, "Did not receive flow_removed message")
+        self.assertEqual(flow_removed.cookie, flow_mod_msg.cookie)
+        self.assertEqual(flow_removed.reason, ofp.OFPRR_DELETE)
+        self.assertEqual(flow_removed.packet_count, num_sends)
+        self.assertEqual(flow_removed.byte_count, num_sends * len(str(pkt)))
diff --git a/tests/load.py b/tests/load.py
index 0f74d56..dcd873b 100644
--- a/tests/load.py
+++ b/tests/load.py
@@ -82,6 +82,7 @@
 
         # Create packet out and send to port lb_port + 1
         msg = message.packet_out()
+        msg.in_port = lb_port
         msg.data = str(pkt)
         act = action.action_output()
         act.port = lb_port + 1
@@ -176,6 +177,7 @@
 
                logging.info("PKT OUT test with %s, port %s" % (opt, dp_port))
                msg = message.packet_out()
+               msg.in_port = ofp.OFPP_NONE
                msg.data = str(outpkt)
                act = action.action_output()
                act.port = dp_port
diff --git a/tests/pktact.py b/tests/pktact.py
index 34654d1..ff6927f 100644
--- a/tests/pktact.py
+++ b/tests/pktact.py
@@ -870,6 +870,12 @@
                         "Could not generate flow match from pkt")
         act = action.action_output()
 
+        # Clear OFPPC_NO_FLOOD on each port
+        for of_port in of_ports:
+            rv = port_config_set(self.controller, of_port,
+                                 0, ofp.OFPPC_NO_FLOOD)
+            self.assertEqual(rv, 0, "Failed to set port config")
+
         for idx in range(len(of_ports)):
             rv = delete_all_flows(self.controller)
             self.assertEqual(rv, 0, "Failed to delete all flows")
@@ -907,6 +913,14 @@
             rv = port_config_set(self.controller, no_flood_port,
                                  0, ofp.OFPPC_NO_FLOOD)
             self.assertEqual(rv, 0, "Failed to reset port config")
+            self.assertEqual(do_barrier(self.controller), 0, "Barrier failed")
+
+            # Check that packets are now flooded to no_flood_port
+            logging.info("Sending packet to dp port " + str(ingress_port))
+            self.dataplane.send(ingress_port, str(pkt))
+            no_ports = set([ingress_port])
+            yes_ports = set(of_ports).difference(no_ports)
+            receive_pkt_check(self.dataplane, pkt, yes_ports, no_ports, self)
 
             #@todo Should check no other packets received
 
@@ -1095,6 +1109,8 @@
     on port specified by this flow.
     3. Add wildcard flow with even higher priority, verify packet received
     on port specified by this flow.
+    4. Add wildcard flow with lower priority, verify packet received
+    on port specified by the highest priority flow.
     """
 
     def runTest(self):
@@ -1117,6 +1133,11 @@
         # priority
         self.installFlow(1001, of_ports[0], of_ports[3])
         self.verifyFlow(of_ports[0], of_ports[3])
+        # Install a flow with wildcards for our packet with lower
+        # priority
+        self.installFlow(999, of_ports[0], of_ports[1],
+                         wildcards=ofp.OFPFW_DL_SRC)
+        self.verifyFlow(of_ports[0], of_ports[3])
         
 
 class WildcardPriorityWithDelete(SingleWildcardMatchPriority):
@@ -1546,6 +1567,21 @@
         flow_match_test(self, config["port_map"], pkt=pkt, exp_pkt=exp_pkt, 
                         action_list=acts, max_test=2)
 
+class ModifyL4SrcUdp(BaseMatchCase):
+    """
+    Modify the source UDP port of a UDP packet
+    """
+    def runTest(self):
+        sup_acts = self.supported_actions
+        if not (sup_acts & 1 << ofp.OFPAT_SET_TP_DST):
+            skip_message_emit(self, "ModifyL4SrcUdp test")
+            return
+
+        (pkt, exp_pkt, acts) = pkt_action_setup(self, mod_fields=['udp_sport'],
+                                                check_test_params=True, tp="udp")
+        flow_match_test(self, config["port_map"], pkt=pkt, exp_pkt=exp_pkt,
+                        action_list=acts, max_test=2)
+
 class ModifyL4Dst(BaseMatchCase):
     """
     Modify the dest TCP port of a TCP packet (TP1)
@@ -1561,6 +1597,21 @@
         flow_match_test(self, config["port_map"], pkt=pkt, exp_pkt=exp_pkt, 
                         action_list=acts, max_test=2)
 
+class ModifyL4DstUdp(BaseMatchCase):
+    """
+    Modify the dest UDP port of a UDP packet
+    """
+    def runTest(self):
+        sup_acts = self.supported_actions
+        if not (sup_acts & 1 << ofp.OFPAT_SET_TP_DST):
+            skip_message_emit(self, "ModifyL4DstUdp test")
+            return
+
+        (pkt, exp_pkt, acts) = pkt_action_setup(self, mod_fields=['udp_dport'],
+                                                check_test_params=True, tp="udp")
+        flow_match_test(self, config["port_map"], pkt=pkt, exp_pkt=exp_pkt,
+                        action_list=acts, max_test=2)
+
 class ModifyTOS(BaseMatchCase):
     """
     Modify the IP type of service of an IP packet (TP1)
@@ -1673,6 +1724,40 @@
         flow_match_test(self, config["port_map"], pkt=pkt, exp_pkt=exp_pkt, 
                         action_list=acts, max_test=2, egr_count=-1)
 
+class ModifyAll(BaseMatchCase):
+    """
+    Modify all supported fields and output to a port
+    """
+    def runTest(self):
+        sup_acts = self.supported_actions
+
+        sup_map = {
+            "dl_dst" : ofp.OFPAT_SET_DL_DST,
+            "dl_src" : ofp.OFPAT_SET_DL_SRC,
+            "dl_vlan_enable" : ofp.OFPAT_SET_VLAN_VID,
+            "dl_vlan" : ofp.OFPAT_SET_VLAN_VID,
+            "dl_vlan_pcp" : ofp.OFPAT_SET_VLAN_PCP,
+            "ip_src" : ofp.OFPAT_SET_NW_SRC,
+            "ip_dst" : ofp.OFPAT_SET_NW_DST,
+            "ip_tos" : ofp.OFPAT_SET_NW_TOS,
+            "tcp_sport" : ofp.OFPAT_SET_TP_SRC,
+            "tcp_dport" : ofp.OFPAT_SET_TP_DST,
+        }
+
+        mod_fields = [field for (field, bit) in sup_map.items() if (sup_acts & 1 << bit)]
+        random.shuffle(mod_fields)
+        start_field_vals = { "dl_vlan_enable" : True }
+        mod_field_vals = { "dl_vlan_enable" : True }
+        logging.info("modifying fields: %s" % repr(mod_fields))
+
+        (pkt, exp_pkt, acts) = pkt_action_setup(self,
+                                                mod_fields=mod_fields,
+                                                start_field_vals=start_field_vals,
+                                                mod_field_vals=mod_field_vals,
+                                                check_test_params=True)
+        flow_match_test(self, config["port_map"], pkt=pkt, exp_pkt=exp_pkt, 
+                        action_list=acts, max_test=2)
+
 class FlowToggle(BaseMatchCase):
     """
     Add flows to the table and modify them repeatedly
@@ -1961,16 +2046,6 @@
         # - VLAN?
         # - action
 
-    def pktToStr(self, pkt):
-        from cStringIO import StringIO
-        backup = sys.stdout
-        sys.stdout = StringIO()
-        pkt.show2()
-        out = sys.stdout.getvalue() 
-        sys.stdout.close() 
-        sys.stdout = backup
-        return out
-
     def createMatch(self, **kwargs):
         match = ofp.ofp_match()
         match.wildcards = ofp.OFPFW_ALL
@@ -2014,7 +2089,7 @@
         logging.info("Ingress %s to egress %s" % 
                        (str(ingress_port), str(egress_port)))
         logging.info("Packet:")
-        logging.info(self.pktToStr(pkt))
+        logging.info(inspect_packet(pkt))
 
         match.in_port = ingress_port
 
@@ -2078,7 +2153,7 @@
             if str_pkt != str_rcv_pkt:
                 logging.error("Response packet does not match send packet")
                 logging.info("Response:")
-                logging.info(self.pktToStr(scapy.Ether(rcv_pkt)))
+                logging.info(inspect_packet(scapy.Ether(rcv_pkt)))
             self.assertEqual(str_pkt, str_rcv_pkt,
                              'Response packet does not match send packet')
         elif expected_result == self.RESULT_NOMATCH:
diff --git a/tests/port_stats.py b/tests/port_stats.py
index 670e31f..89b2cf6 100644
--- a/tests/port_stats.py
+++ b/tests/port_stats.py
@@ -78,6 +78,21 @@
     logging.info("Port %d stats count: tx %d rx %d" % (port, packet_sent, packet_recv))
     return packet_sent, packet_recv
 
+def getAllStats(obj):
+    stat_req = message.port_stats_request()
+    stat_req.port_no = ofp.OFPP_NONE
+
+    logging.info("Sending all port stats request")
+    response, pkt = obj.controller.transact(stat_req, timeout=2)
+    obj.assertTrue(response is not None, 
+                    "No response to stats request")
+    obj.assertTrue(len(response.stats) >= 3,
+                    "Did not receive all port stats reply")
+    stats = {}
+    for item in response.stats:
+        stats[ item.port_no ] = ( item.tx_packets, item.rx_packets )
+    return stats
+
 def verifyStats(obj, port, test_timeout, packet_sent, packet_recv):
     stat_req = message.port_stats_request()
     stat_req.port_no = port
@@ -179,7 +194,6 @@
         verifyStats(self, ingress_port, test_timeout, initTxInPort, initRxInPort + num_sends)
         verifyStats(self, egress_port, test_timeout, initTxOutPort + num_sends, initRxOutPort)
 
-
 class MultiFlowStats(base_tests.SimpleDataPlane):
     """
     Verify flow stats are properly retrieved.
@@ -260,3 +274,80 @@
                     initTxOutPort1 + num_pkt1s, initRxOutPort1)
         verifyStats(self, egress_port2, test_timeout,
                     initTxOutPort2 + num_pkt2s, initRxOutPort2)
+
+class AllPortStats(base_tests.SimpleDataPlane):
+    """
+    Verify all port stats are properly retrieved.
+
+    First, get stats from each port. Then get all port stats, verify
+    consistency with single port stats.
+    """
+
+    # TODO: This is copied from MultiFlowStats. Need to combine.
+    def buildFlowModMsg(self, pkt, ingress_port, egress_port):
+        match = packet_to_flow_match(self, pkt)
+        match.wildcards &= ~ofp.OFPFW_IN_PORT
+        self.assertTrue(match is not None, 
+                        "Could not generate flow match from pkt")
+        match.in_port = ingress_port
+        
+        flow_mod_msg = message.flow_mod()
+        flow_mod_msg.match = match
+        flow_mod_msg.cookie = random.randint(0,9007199254740992)
+        flow_mod_msg.buffer_id = 0xffffffff
+        flow_mod_msg.idle_timeout = 0
+        flow_mod_msg.hard_timeout = 0
+        act = action.action_output()
+        act.port = egress_port
+        self.assertTrue(flow_mod_msg.actions.add(act), "Could not add action")
+
+        logging.info("Ingress " + str(ingress_port) + 
+                       " to egress " + str(egress_port))
+
+        return flow_mod_msg
+
+    def runTest(self):
+        # TODO: set from command-line parameter
+        test_timeout = 60
+
+        of_ports = config["port_map"].keys()
+        of_ports.sort()
+        self.assertTrue(len(of_ports) >= 3, "Not enough ports for test")
+        port0 = of_ports[0];
+        port1 = of_ports[1];
+        port2 = of_ports[2];
+
+        # construct some packets and flows, send to switch
+        pkt1 = simple_tcp_packet()
+        flow_mod_msg1 = self.buildFlowModMsg(pkt1, port0, port1)
+       
+        pkt2 = simple_tcp_packet(dl_src='0:7:7:7:7:7')
+        flow_mod_msg2 = self.buildFlowModMsg(pkt2, port0, port2)
+       
+        logging.info("Inserting flow1")
+        rv = self.controller.message_send(flow_mod_msg1)
+        self.assertTrue(rv != -1, "Error installing flow mod")
+        logging.info("Inserting flow2")
+        rv = self.controller.message_send(flow_mod_msg2)
+        self.assertTrue(rv != -1, "Error installing flow mod")
+        self.assertEqual(do_barrier(self.controller), 0, "Barrier failed")
+
+        num_pkt1s = random.randint(5,10)
+        logging.info("Sending " + str(num_pkt1s) + " pkt1s")
+        num_pkt2s = random.randint(10,15)
+        logging.info("Sending " + str(num_pkt2s) + " pkt2s")
+        for i in range(0,num_pkt1s):
+            sendPacket(self, pkt1, port0, port1, test_timeout)
+        for i in range(0,num_pkt2s):
+            sendPacket(self, pkt2, port0, port2, test_timeout)
+
+        # get individual port stats count
+        port_stats = {}
+        port_stats[ port0 ] = getStats(self, port0)
+        port_stats[ port1 ] = getStats(self, port1)
+        port_stats[ port2 ] = getStats(self, port2)
+
+        all_stats = getAllStats(self)
+        self.assertEqual(port_stats[ port0 ], all_stats[ port0 ])
+        self.assertEqual(port_stats[ port1 ], all_stats[ port1 ])
+        self.assertEqual(port_stats[ port2 ], all_stats[ port2 ])