Merged from Ken Cs flow_stats dev branch
diff --git a/README b/README
index 5d1b5c5..ea5b7a4 100644
--- a/README
+++ b/README
@@ -1,5 +1,6 @@
 OpenFlow Testing Framework
 July, 2010
+Last updated March 2012
 
 Copyright (c) 2010 The Board of Trustees of The Leland Stanford 
 Junior University
@@ -30,7 +31,7 @@
 
     You can check out OFTest with git with the following command:
 
-    git clone git://openflow.org/oftest
+    git clone git://github.com/floodlight/oftest
 
 Introduction
 ++++++++++++
@@ -62,15 +63,15 @@
 
     Currently, switches must be running version 1.0 of OpenFlow. 
 
-      # git clone yuba:/usr/local/git/openflow-projects/oftest
+      # git clone git://github.com/floodlight/oftest
       # cd oftest/tools/munger
       # make install
       # cd ../../tests
          Make sure the switch you want to test is running --
          see (4) below for the reference switch example.
       # ./oft --list
-      # sudo ./oft
-      # sudo ./oft --verbose --log-file=""    
+      # sudo ./oft --test-spec=Echo
+      # sudo ./oft --verbose --log-file=""
       # sudo ./oft --test-spec=<mod> --platform=remote --host=...
 
 Longer Start
@@ -81,8 +82,8 @@
         * Root privilege on host running oft
         * Switch running OpenFlow 1.0 and attempting to connect 
           to a controller on the machine running oft.
-        * Python 2.5.  You can run platforms using eth interfaces
-          with Python 2.4.
+        * Python 2.5 or 2.6.  You can run platforms using eth interfaces
+          with Python 2.4.  Python 2.7 may work.
         * Python setup tools (e.g.: sudo apt-get install python-setuptools)
         * oftest checked out (called <oftest> here)
         * scapy installed:  http://www.secdev.org/projects/scapy/
@@ -139,6 +140,10 @@
         4F. To clean up the virtual ethernet interfaces, use
             sudo rmmod veth
 
+        New tools allow you to run an OVS instance as well.  See
+        oftest/tools/ovs-ctl.  You will need to install a version of
+        openvswitch.  See http://openvswitch.org/.
+
     5.  Run oft
         See Warning above; requires sudo to control the dataplane
         cd <oftest>/tests
diff --git a/tests/basic.py b/tests/basic.py
index ac4d89f..220f7c8 100644
--- a/tests/basic.py
+++ b/tests/basic.py
@@ -209,36 +209,39 @@
         do_barrier(self.controller)
 
         for of_port in basic_port_map.keys():
-            basic_logger.info("PKT IN test, port " + str(of_port))
-            pkt = simple_tcp_packet()
-            self.dataplane.send(of_port, str(pkt))
-            #@todo Check for unexpected messages?
-            count = 0
-            while True:
-                (response, raw) = self.controller.poll(ofp.OFPT_PACKET_IN, 2)
-                if not response:  # Timeout
-                    break
-                if str(pkt) == response.data:  # Got match
-                    break
-                if not basic_config["relax"]:  # Only one attempt to match
-                    break
-                count += 1
-                if count > 10:   # Too many tries
-                    break
+            for pkt, pt in [
+               (simple_tcp_packet(), "simple TCP packet"),
+               (simple_eth_packet(), "simple Ethernet packet"),
+               (simple_eth_packet(pktlen=40), "tiny Ethernet packet")]:
 
-            self.assertTrue(response is not None, 
-                            'Packet in message not received on port ' + 
-                            str(of_port))
-            if str(pkt) != response.data:
-                basic_logger.debug("pkt  len " + str(len(str(pkt))) +
-                                   ": " + str(pkt))
-                basic_logger.debug("resp len " + 
-                                   str(len(str(response.data))) + 
-                                   ": " + str(response.data))
+               basic_logger.info("PKT IN test with %s, port %s" % (pt, of_port))
+               self.dataplane.send(of_port, str(pkt))
+               #@todo Check for unexpected messages?
+               count = 0
+               while True:
+                   (response, raw) = self.controller.poll(ofp.OFPT_PACKET_IN, 2)
+                   if not response:  # Timeout
+                       break
+                   if str(pkt) == response.data[:len(str(pkt))]:  # Got match
+                       break
+                   if not basic_config["relax"]:  # Only one attempt to match
+                       break
+                   count += 1
+                   if count > 10:   # Too many tries
+                       break
 
-            self.assertEqual(str(pkt), response.data,
-                             'Response packet does not match send packet' +
-                             ' for port ' + str(of_port))
+               self.assertTrue(response is not None, 
+                               'Packet in message not received on port ' + 
+                               str(of_port))
+               if str(pkt) != response.data[:len(str(pkt))]:
+                   basic_logger.debug("pkt  len " + str(len(str(pkt))) +
+                                      ": " + str(pkt))
+                   basic_logger.debug("resp len " + 
+                                      str(len(str(response.data))) + 
+                                      ": " + str(response.data))
+                   self.assertTrue(False,
+                                   'Response packet does not match send packet' +
+                                   ' for port ' + str(of_port))
 
 class PacketOut(SimpleDataPlane):
     """
@@ -256,35 +259,40 @@
         self.assertEqual(rc, 0, "Failed to delete all flows")
 
         # These will get put into function
-        outpkt = simple_tcp_packet()
         of_ports = basic_port_map.keys()
         of_ports.sort()
         for dp_port in of_ports:
-            msg = message.packet_out()
-            msg.data = str(outpkt)
-            act = action.action_output()
-            act.port = dp_port
-            self.assertTrue(msg.actions.add(act), 'Could not add action to msg')
+            for outpkt, opt in [
+               (simple_tcp_packet(), "simple TCP packet"),
+               (simple_eth_packet(), "simple Ethernet packet"),
+               (simple_eth_packet(pktlen=40), "tiny Ethernet packet")]:
 
-            basic_logger.info("PacketOut to: " + str(dp_port))
-            rv = self.controller.message_send(msg)
-            self.assertTrue(rv == 0, "Error sending out message")
+               basic_logger.info("PKT OUT test with %s, port %s" % (opt, dp_port))
+               msg = message.packet_out()
+               msg.data = str(outpkt)
+               act = action.action_output()
+               act.port = dp_port
+               self.assertTrue(msg.actions.add(act), 'Could not add action to msg')
 
-            exp_pkt_arg = None
-            exp_port = None
-            if basic_config["relax"]:
-                exp_pkt_arg = outpkt
-                exp_port = dp_port
-            (of_port, pkt, pkt_time) = self.dataplane.poll(timeout=1, 
-                                                           port_number=exp_port,
-                                                           exp_pkt=exp_pkt_arg)
+               basic_logger.info("PacketOut to: " + str(dp_port))
+               rv = self.controller.message_send(msg)
+               self.assertTrue(rv == 0, "Error sending out message")
 
-            self.assertTrue(pkt is not None, 'Packet not received')
-            basic_logger.info("PacketOut: got pkt from " + str(of_port))
-            if of_port is not None:
-                self.assertEqual(of_port, dp_port, "Unexpected receive port")
-            self.assertEqual(str(outpkt), str(pkt),
-                             'Response packet does not match send packet')
+               exp_pkt_arg = None
+               exp_port = None
+               if basic_config["relax"]:
+                   exp_pkt_arg = outpkt
+                   exp_port = dp_port
+               (of_port, pkt, pkt_time) = self.dataplane.poll(timeout=1, 
+                                                              port_number=exp_port,
+                                                              exp_pkt=exp_pkt_arg)
+
+               self.assertTrue(pkt is not None, 'Packet not received')
+               basic_logger.info("PacketOut: got pkt from " + str(of_port))
+               if of_port is not None:
+                   self.assertEqual(of_port, dp_port, "Unexpected receive port")
+               self.assertEqual(str(outpkt), str(pkt)[:len(str(outpkt))],
+                                'Response packet does not match send packet')
 
 class FlowStatsGet(SimpleProtocol):
     """
diff --git a/tests/flow_stats.py b/tests/flow_stats.py
index 473bda2..f7a8ad2 100644
--- a/tests/flow_stats.py
+++ b/tests/flow_stats.py
@@ -154,7 +154,8 @@
             pa_logger.info("Sending packet to dp port " + 
                            str(ingress_port))
             self.dataplane.send(ingress_port, str(pkt))
-            (rcv_port, rcv_pkt, pkt_time) = self.dataplane.poll(timeout=test_timeout)
+            (rcv_port, rcv_pkt, pkt_time) = self.dataplane.poll(timeout=
+                                                                test_timeout)
             self.assertTrue(rcv_pkt is not None, "Did not receive packet")
             pa_logger.debug("Packet len " + str(len(pkt)) + " in on " + 
                             str(rcv_port))
@@ -212,7 +213,8 @@
         pa_logger.info("Sending packet to dp port " + 
                        str(ingress_port))
         self.dataplane.send(ingress_port, str(pkt))
-        (rcv_port, rcv_pkt, pkt_time) = self.dataplane.poll(timeout=test_timeout)
+        (rcv_port, rcv_pkt, pkt_time) = self.dataplane.poll(timeout=
+                                                            test_timeout)
         self.assertTrue(rcv_pkt is not None, "Did not receive packet")
         pa_logger.debug("Packet len " + str(len(pkt)) + " in on " + 
                         str(rcv_port))
@@ -238,7 +240,8 @@
             do_barrier(self.controller)
 
             (response, raw) = self.controller.poll(ofp.OFPT_STATS_REPLY, 2)
-            self.assertTrue(len(response.stats) >= 1, "Did not receive flow stats reply")
+            self.assertTrue(len(response.stats) >= 1,
+                            "Did not receive flow stats reply")
             total_packets = 0
             for obj in response.stats:
                 # TODO: pad1 and pad2 fields may be nonzero, is this a bug?
@@ -343,7 +346,8 @@
         pa_logger.info("Sending packet to dp port " + 
                        str(ingress_port))
         self.dataplane.send(ingress_port, str(pkt))
-        (rcv_port, rcv_pkt, pkt_time) = self.dataplane.poll(timeout=test_timeout)
+        (rcv_port, rcv_pkt, pkt_time) = self.dataplane.poll(timeout=
+                                                            test_timeout)
         self.assertTrue(rcv_pkt is not None, "Did not receive packet")
         pa_logger.debug("Packet len " + str(len(pkt)) + " in on " + 
                         str(rcv_port))
@@ -370,7 +374,8 @@
             do_barrier(self.controller)
 
             (response, raw) = self.controller.poll(ofp.OFPT_STATS_REPLY, 2)
-            self.assertTrue(len(response.stats) == 1, "Did not receive flow stats reply")
+            self.assertTrue(len(response.stats) == 1, 
+                            "Did not receive flow stats reply")
             for obj in response.stats:
                 self.assertTrue(obj.flow_count == flow_count,
                                 "Flow count " + str(obj.flow_count) +
diff --git a/tests/oft b/tests/oft
index 48816a1..c397372 100755
--- a/tests/oft
+++ b/tests/oft
@@ -221,8 +221,8 @@
     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
-        default) or a comma separated list of:\n
-        module            Run all tests in the named module\n
+        default) or a comma separated list of:
+        module            Run all tests in the named module
         testcase          Run tests in all modules with the name testcase
         module.testcase   Run the specific test case
         """
diff --git a/tests/pktact.py b/tests/pktact.py
index 86fc44e..9230afd 100644
--- a/tests/pktact.py
+++ b/tests/pktact.py
@@ -953,62 +953,104 @@
         (pkt, exp_pkt, acts) = pkt_action_setup(self, mod_fields=['ip_tos'],
                                                 check_test_params=True)
         flow_match_test(self, pa_port_map, pkt=pkt, exp_pkt=exp_pkt, 
-                        action_list=acts, max_test=2)
+                        action_list=acts, max_test=2, egr_count=-1)
 
 class ModifyL2DstMC(BaseMatchCase):
     """
     Modify the L2 dest and send to 2 ports
-
-    Uses egr_count test parameter; defaults to 2
     """
     def runTest(self):
         sup_acts = supported_actions_get(self)
         if not (sup_acts & 1 << ofp.OFPAT_SET_DL_DST):
-            skip_message_emit(self, "ModifyL2dst test")
+            skip_message_emit(self, "ModifyL2dstMC test")
             return
 
-        egr_count = test_param_get(self.config, 'egr_count', default=2)
         (pkt, exp_pkt, acts) = pkt_action_setup(self, mod_fields=['dl_dst'],
                                                 check_test_params=True)
         flow_match_test(self, pa_port_map, pkt=pkt, exp_pkt=exp_pkt, 
-                        action_list=acts, max_test=2, egr_count=egr_count)
+                        action_list=acts, max_test=2, egr_count=-1)
+
+class ModifyL2DstIngress(BaseMatchCase):
+    """
+    Modify the L2 dest and send to the ingress port
+    """
+    def runTest(self):
+        sup_acts = supported_actions_get(self)
+        if not (sup_acts & 1 << ofp.OFPAT_SET_DL_DST):
+            skip_message_emit(self, "ModifyL2dstMC test")
+            return
+
+        (pkt, exp_pkt, acts) = pkt_action_setup(self, mod_fields=['dl_dst'],
+                                                check_test_params=True)
+        flow_match_test(self, pa_port_map, pkt=pkt, exp_pkt=exp_pkt, 
+                        action_list=acts, max_test=2, egr_count=0,
+                        ing_port=True)
+
+class ModifyL2DstIngressMC(BaseMatchCase):
+    """
+    Modify the L2 dest and send to the ingress port
+    """
+    def runTest(self):
+        sup_acts = supported_actions_get(self)
+        if not (sup_acts & 1 << ofp.OFPAT_SET_DL_DST):
+            skip_message_emit(self, "ModifyL2dstMC test")
+            return
+
+        (pkt, exp_pkt, acts) = pkt_action_setup(self, mod_fields=['dl_dst'],
+                                                check_test_params=True)
+        flow_match_test(self, pa_port_map, pkt=pkt, exp_pkt=exp_pkt, 
+                        action_list=acts, max_test=2, egr_count=-1,
+                        ing_port=True)
 
 class ModifyL2SrcMC(BaseMatchCase):
     """
     Modify the source MAC address (TP1) and send to multiple
-
-    Uses egr_count test parameter; defaults to 2
     """
     def runTest(self):
         sup_acts = supported_actions_get(self)
         if not (sup_acts & 1 << ofp.OFPAT_SET_DL_SRC):
-            skip_message_emit(self, "ModifyL2Src test")
+            skip_message_emit(self, "ModifyL2SrcMC test")
             return
 
-        egr_count = test_param_get(self.config, 'egr_count', default=2)
         (pkt, exp_pkt, acts) = pkt_action_setup(self, mod_fields=['dl_src'],
                                                 check_test_params=True)
         flow_match_test(self, pa_port_map, pkt=pkt, exp_pkt=exp_pkt, 
-                        action_list=acts, max_test=2, egr_count=egr_count)
+                        action_list=acts, max_test=2, egr_count=-1)
 
 class ModifyL2SrcDstMC(BaseMatchCase):
     """
     Modify the L2 source and dest and send to 2 ports
-
-    Uses egr_count test parameter; defaults to 2
     """
     def runTest(self):
         sup_acts = supported_actions_get(self)
-        if not (sup_acts & 1 << ofp.OFPAT_SET_DL_DST):
-            skip_message_emit(self, "ModifyL2dst test")
+        if (not (sup_acts & 1 << ofp.OFPAT_SET_DL_DST) or
+                not (sup_acts & 1 << ofp.OFPAT_SET_DL_SRC)):
+            skip_message_emit(self, "ModifyL2SrcDstMC test")
             return
 
-        egr_count = test_param_get(self.config, 'egr_count', default=2)
         mod_fields = ['dl_dst', 'dl_src']
         (pkt, exp_pkt, acts) = pkt_action_setup(self, mod_fields=mod_fields,
                                                 check_test_params=True)
         flow_match_test(self, pa_port_map, pkt=pkt, exp_pkt=exp_pkt, 
-                        action_list=acts, max_test=2, egr_count=egr_count)
+                        action_list=acts, max_test=2, egr_count=-1)
+
+class ModifyL2DstVIDMC(BaseMatchCase):
+    """
+    Modify the L2 dest and send to 2 ports
+    """
+    def runTest(self):
+        sup_acts = supported_actions_get(self)
+        if (not (sup_acts & 1 << ofp.OFPAT_SET_DL_DST) or
+                not (sup_acts & 1 << ofp.OFPAT_SET_VLAN_VID)):
+            skip_message_emit(self, "ModifyL2DstVIDMC test")
+            return
+
+        mod_fields = ['dl_dst', 'dl_vlan']
+        (pkt, exp_pkt, acts) = pkt_action_setup(self, 
+             start_field_vals={'dl_vlan_enable':True}, mod_fields=mod_fields,
+                                                check_test_params=True)
+        flow_match_test(self, pa_port_map, pkt=pkt, exp_pkt=exp_pkt, 
+                        action_list=acts, max_test=2, egr_count=-1)
 
 
 #@todo Need to implement tagged versions of the above tests
diff --git a/tests/testutils.py b/tests/testutils.py
index 4c7420c..5a99a00 100644
--- a/tests/testutils.py
+++ b/tests/testutils.py
@@ -158,6 +158,16 @@
 
     return pkt
 
+def simple_eth_packet(pktlen=60,
+                      dl_dst='00:01:02:03:04:05',
+                      dl_src='01:80:c2:00:00:00',
+                      dl_type=0x88cc):
+    pkt = scapy.Ether(dst=dl_dst, src=dl_src, type=dl_type)
+
+    pkt = pkt/("0" * (pktlen - len(pkt)))
+
+    return pkt
+
 def do_barrier(ctrl):
     b = message.barrier_request()
     ctrl.transact(b)
@@ -246,7 +256,7 @@
                              "Unexpected pkt on port " + str(ofport))
 
 
-def receive_pkt_verify(parent, egr_ports, exp_pkt):
+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
@@ -264,14 +274,18 @@
 
     # 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=egr_port, timeout=1, exp_pkt=exp_pkt_arg)
+            port_number=check_port, timeout=1, exp_pkt=exp_pkt_arg)
 
         if rcv_pkt is None:
-            parent.logger.error("ERROR: No packet received from " + str(egr_port))
+            parent.logger.error("ERROR: No packet received from " + 
+                                str(check_port))
 
         parent.assertTrue(rcv_pkt is not None,
-                          "Did not receive packet port " + str(egr_port))
+                          "Did not receive packet port " + str(check_port))
         parent.logger.debug("Packet len " + str(len(rcv_pkt)) + " in on " + 
                             str(rcv_port))
 
@@ -282,7 +296,7 @@
             parent.logger.debug("Received len " + str(len(rcv_pkt)) + ": "
                                 + str(rcv_pkt).encode('hex'))
         parent.assertEqual(str(exp_pkt), str(rcv_pkt),
-                           "Packet match error on port " + str(egr_port))
+                           "Packet match error on port " + str(check_port))
 
 def match_verify(parent, req_match, res_match):
     """
@@ -490,7 +504,7 @@
 
     if exp_pkt is None:
         exp_pkt = pkt
-    receive_pkt_verify(parent, egr_ports, exp_pkt)
+    receive_pkt_verify(parent, egr_ports, exp_pkt, ing_port)
 
     if check_expire:
         #@todo Not all HW supports both pkt and byte counters
@@ -506,6 +520,9 @@
     @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)): 
@@ -519,7 +536,7 @@
     
 def flow_match_test(parent, port_map, wildcards=0, dl_vlan=-1, pkt=None, 
                     exp_pkt=None, action_list=None, check_expire=False, 
-                    max_test=0, egr_count=1):
+                    max_test=0, egr_count=1, ing_port=False):
     """
     Run flow_match_test_port_pair on all port pairs
 
@@ -532,16 +549,22 @@
     @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 message
+    @param egr_count Number of egress ports; -1 means get from config w/ dflt 2
     """
     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(parent.config, '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")