Merge branch 'kenc'
diff --git a/README b/README
index a8cd199..a8552bf 100644
--- a/README
+++ b/README
@@ -86,6 +86,9 @@
         * oftest checked out (called <oftest> here)
         * scapy installed:  http://www.secdev.org/projects/scapy/
           'sudo apt-get install scapy' should work on Debian.
+        * pypcap installed:  http://code.google.com/p/pypcap/ (optional)
+          'sudo apt-get install python-pypcap' should work on Debian.
+          Tests using VLAN tags may fail without pypcap.
         * tcpdump installed (optional, but scapy will complain if it's
           not there)
         * Doxygen and doxypy for document generation (optional)
@@ -248,6 +251,9 @@
     fields in the packets.  With the local platform, you can use wireshark
     on the loopback interface as well as the dataplane veth interfaces.
 
+    3.  If tests dealing with VLANs fail unexpectedly then try installing
+    pypcap (see Longer Start above).
+
 Adding Your Own Test Cases
 ++++++++++++++++++++++++++
 
diff --git a/src/python/oftest/dataplane.py b/src/python/oftest/dataplane.py
index 8d9b1a2..828f584 100644
--- a/src/python/oftest/dataplane.py
+++ b/src/python/oftest/dataplane.py
@@ -27,6 +27,13 @@
 from oft_assert import oft_assert
 from ofutils import *
 
+have_pypcap = False
+try:
+    import pcap
+    have_pypcap = True
+except:
+    pass
+
 ##@todo Find a better home for these identifiers (dataplane)
 RCV_SIZE_DEFAULT = 4096
 ETH_P_ALL = 0x03
@@ -82,8 +89,8 @@
             self.socket = self.interface_open(interface_name)
         except:
             self.logger.info("Could not open socket")
-            sys.exit(1)
-        self.logger.info("Openned port monitor socket")
+            raise
+        self.logger.info("Opened port monitor (class %s)", type(self).__name__)
         self.parent = parent
 
     def interface_open(self, interface_name):
@@ -208,6 +215,78 @@
         print prefix + "Pkts total:    " + str(self.packets_total)
         print prefix + "socket:        " + str(self.socket)
 
+class DataPlanePortPcap(DataPlanePort):
+    """
+    Alternate port implementation using libpcap. This is required for recent
+    versions of Linux (such as Linux 3.2 included in Ubuntu 12.04) which
+    offload the VLAN tag, so it isn't in the data returned from a read on a raw
+    socket. libpcap understands how to read the VLAN tag from the kernel.
+    """
+
+    def __init__(self, interface_name, port_number, parent, max_pkts=1024):
+        DataPlanePort.__init__(self, interface_name, port_number, parent, max_pkts)
+
+    def interface_open(self, interface_name):
+        """
+        Open a PCAP interface.
+        """
+        self.pcap = pcap.pcap(interface_name)
+        self.pcap.setnonblock()
+        return self.pcap.fileno()
+
+    def run(self):
+        """
+        Activity function for class
+        """
+        self.running = True
+        while self.running:
+            try:
+                sel_in, sel_out, sel_err = select.select([self.socket], [], [], 1)
+            except:
+                print sys.exc_info()
+                self.logger.error("Select error, exiting")
+                break
+
+            if not self.running:
+                break
+
+            if (sel_in is None) or (len(sel_in) == 0):
+                continue
+
+            # 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 " +
+                                      str(self.port_number))
+
+                    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((rcvmsg, rcvtime))
+                    self.packets_total += 1
+                self.parent.pkt_sync.notify_all()
+
+        self.logger.info("Thread exit")
+
+    def kill(self):
+        """
+        Terminate the running thread
+        """
+        self.logger.debug("Port monitor kill")
+        self.running = False
+        # pcap object is closed on GC.
+
+    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
+        """
+        return self.pcap.inject(packet, len(packet))
 
 class DataPlane:
     """
@@ -238,7 +317,11 @@
         # We use the DataPlanePort class defined here by 
         # default for all port traffic:
         #
-        self.dppclass = DataPlanePort
+        if have_pypcap:
+            self.dppclass = DataPlanePortPcap
+        else:
+            self.logger.warning("Missing pypcap, VLAN tests may fail. See README for installation instructions.")
+            self.dppclass = DataPlanePort
 
         ############################################################
         #
diff --git a/src/python/oftest/parse.py b/src/python/oftest/parse.py
index 8826c0c..a3421a1 100644
--- a/src/python/oftest/parse.py
+++ b/src/python/oftest/parse.py
@@ -245,8 +245,10 @@
     except:
         icmp = None
 
-    # @todo arp is not yet supported
-    arp = None
+    try:
+        arp = ether[scapy.ARP]
+    except:
+        arp = None
     return (dot1q, ip, tcp, udp, icmp, arp)
 
 def packet_to_flow_match(packet, pkt_format="L2"):
@@ -328,7 +330,14 @@
         match.nw_proto = 1
         match.tp_src = icmp.type
         match.tp_dst = icmp.code
+        match.wildcards &= ~OFPFW_NW_PROTO
 
-    #@todo Implement ARP fields
+    if arp:
+        match.nw_proto = arp.op
+        match.wildcards &= ~OFPFW_NW_PROTO
+        match.nw_src = parse_ip(arp.psrc)
+        match.wildcards &= ~OFPFW_NW_SRC_MASK
+        match.nw_dst = parse_ip(arp.pdst)
+        match.wildcards &= ~OFPFW_NW_DST_MASK
 
     return match
diff --git a/src/python/oftest/testutils.py b/src/python/oftest/testutils.py
index 3be9c32..53c18fe 100644
--- a/src/python/oftest/testutils.py
+++ b/src/python/oftest/testutils.py
@@ -64,7 +64,9 @@
                       ip_dst='192.168.0.2',
                       ip_tos=0,
                       tcp_sport=1234,
-                      tcp_dport=80
+                      tcp_dport=80,
+                      ip_ihl=None,
+                      ip_options=False
                       ):
     """
     Return a simple dataplane TCP packet
@@ -94,14 +96,22 @@
     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)/ \
+            scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ihl=ip_ihl)/ \
             scapy.TCP(sport=tcp_sport, dport=tcp_dport)
     else:
-        pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
-            scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos)/ \
-            scapy.TCP(sport=tcp_sport, dport=tcp_dport)
+        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.TCP(sport=tcp_sport, dport=tcp_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.TCP(sport=tcp_sport, dport=tcp_dport)
 
     pkt = pkt/("D" * (pktlen - len(pkt)))
+    
+    #print pkt.show()
+    #print scapy.Ether(str(pkt)).show()
 
     return pkt
 
diff --git a/tests/basic.py b/tests/basic.py
index 113640a..e4b933e 100644
--- a/tests/basic.py
+++ b/tests/basic.py
@@ -378,6 +378,7 @@
         rv = port_config_set(self.controller, of_port,
                              port_config ^ ofp.OFPPC_NO_FLOOD, ofp.OFPPC_NO_FLOOD)
         self.assertTrue(rv != -1, "Error sending port mod")
+        self.assertEqual(do_barrier(self.controller), 0, "Barrier failed")
 
         # Verify change took place with same feature request
         (hw_addr, port_config2, advert) = \
@@ -392,6 +393,7 @@
         rv = port_config_set(self.controller, of_port, port_config,
                              ofp.OFPPC_NO_FLOOD)
         self.assertTrue(rv != -1, "Error sending port mod")
+        self.assertEqual(do_barrier(self.controller), 0, "Barrier failed")
 
 class PortConfigModErr(base_tests.SimpleProtocol):
     """
diff --git a/tests/flow_query.py b/tests/flow_query.py
index e3dc1a7..13c82c3 100644
--- a/tests/flow_query.py
+++ b/tests/flow_query.py
@@ -563,7 +563,6 @@
             elif a == ofp.OFPAT_ENQUEUE:
                 pass                    # Enqueue actions must come last
             if act:
-                act.max_len = ACTION_MAX_LEN
                 self.actions.add(act)
                 
         p = random.randint(1, 100)
@@ -575,7 +574,6 @@
             # At most 1 ENQUEUE action
             act = action.action_enqueue()
             (act.port, act.queue_id) = rand_pick(valid_queues)
-            act.max_len = ACTION_MAX_LEN
             self.actions.add(act)
         if (((1 << ofp.OFPAT_OUTPUT) & actions_force) != 0 \
             or (p > 33 and p <= 66) \
@@ -591,7 +589,6 @@
             for pi in port_idxs:
                 act = action.action_output()
                 act.port = valid_ports[pi]
-                act.max_len = ACTION_MAX_LEN
                 if act.port != ofp.OFPP_IN_PORT \
                    or wildcard_get(self.match.wildcards, ofp.OFPFW_IN_PORT) == 0:
                     # OUTPUT(IN_PORT) only valid if OFPFW_IN_PORT not wildcarded
diff --git a/tests/flow_stats.py b/tests/flow_stats.py
index 2144278..2a42b94 100644
--- a/tests/flow_stats.py
+++ b/tests/flow_stats.py
@@ -8,6 +8,7 @@
 
 import unittest
 import random
+import copy
 
 from oftest import config
 import oftest.controller as controller
@@ -72,7 +73,7 @@
     Verify that the packet counter has incremented
     """
 
-    def verifyStats(self, match, out_port, test_timeout, packet_count):
+    def verifyStats(self, flow_mod_msg, match, out_port, test_timeout, packet_count):
         stat_req = message.flow_stats_request()
         stat_req.match = match
         stat_req.table_id = 0xff
@@ -90,10 +91,15 @@
             for obj in response.stats:
                 # TODO: pad1 and pad2 fields may be nonzero, is this a bug?
                 # for now, just clear them so the assert is simpler
-                #obj.match.pad1 = 0
-                #obj.match.pad2 = [0, 0]
-                #self.assertEqual(match, obj.match,
-                #                 "Matches do not match")
+                obj.match.pad1 = 0
+                obj.match.pad2 = [0, 0]
+                self.assertEqual(flow_mod_msg.match, obj.match,
+                                 "Matches do not match")
+                self.assertEqual(obj.cookie, flow_mod_msg.cookie)
+                self.assertEqual(obj.priority, flow_mod_msg.priority)
+                self.assertEqual(obj.idle_timeout, flow_mod_msg.idle_timeout)
+                self.assertEqual(obj.hard_timeout, flow_mod_msg.hard_timeout)
+                self.assertEqual(obj.actions, flow_mod_msg.actions)
                 logging.info("Received " + str(obj.packet_count) + " packets")
                 if obj.packet_count == packet_count:
                     all_packets_received = 1
@@ -131,11 +137,12 @@
                        " to egress " + str(egress_port))
         match.in_port = ingress_port
         flow_mod_msg = message.flow_mod()
-        flow_mod_msg.match = match
+        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.idle_timeout = 60000
+        flow_mod_msg.hard_timeout = 65000
+        flow_mod_msg.priority = 100
         act.port = egress_port
         self.assertTrue(flow_mod_msg.actions.add(act), "Could not add action")
        
@@ -146,7 +153,7 @@
         self.assertEqual(do_barrier(self.controller), 0, "Barrier failed")
 
         # no packets sent, so zero packet count
-        self.verifyStats(match, ofp.OFPP_NONE, test_timeout, 0)
+        self.verifyStats(flow_mod_msg, match, ofp.OFPP_NONE, test_timeout, 0)
 
         # send packet N times
         num_sends = random.randint(10,20)
@@ -155,11 +162,11 @@
             sendPacket(self, pkt, ingress_port, egress_port,
                        test_timeout)
 
-        self.verifyStats(match, ofp.OFPP_NONE, test_timeout, num_sends)
-        self.verifyStats(match, egress_port, test_timeout, num_sends)
+        self.verifyStats(flow_mod_msg, match, ofp.OFPP_NONE, test_timeout, num_sends)
+        self.verifyStats(flow_mod_msg, match, egress_port, test_timeout, num_sends)
         for wc in WILDCARD_VALUES:
             match.wildcards = required_wildcards(self) | wc
-            self.verifyStats(match, egress_port, test_timeout, num_sends)
+            self.verifyStats(flow_mod_msg, match, egress_port, test_timeout, num_sends)
 
 
 class TwoFlowStats(base_tests.SimpleDataPlane):
@@ -406,3 +413,48 @@
         # out_port filter for egress_port1
         self.verifyAggFlowStats(match, egress_port2, test_timeout, 
                                 1, num_pkt2s)
+
+class EmptyFlowStats(base_tests.SimpleDataPlane):
+    """
+    Verify the switch replies to a flow stats request when
+    the query doesn't match any flows.
+    """
+    def runTest(self):
+        rc = delete_all_flows(self.controller)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+        match = ofp.ofp_match()
+        match.wildcards = 0
+        stat_req = message.flow_stats_request()
+        stat_req.match = match
+        stat_req.table_id = 0xff
+        stat_req.out_port = ofp.OFPP_NONE
+
+        response, pkt = self.controller.transact(stat_req)
+        self.assertTrue(response is not None,
+                        "No response to stats request")
+        self.assertEquals(len(response.stats), 0)
+        self.assertEquals(response.flags, 0)
+
+class EmptyAggregateStats(base_tests.SimpleDataPlane):
+    """
+    Verify aggregate flow stats are properly retrieved when
+    the query doesn't match any flows.
+    """
+    def runTest(self):
+        rc = delete_all_flows(self.controller)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+        match = ofp.ofp_match()
+        match.wildcards = 0
+        stat_req = message.aggregate_stats_request()
+        stat_req.match = match
+        stat_req.table_id = 0xff
+        stat_req.out_port = ofp.OFPP_NONE
+
+        response, pkt = self.controller.transact(stat_req)
+        self.assertTrue(response is not None,
+                        "No response to stats request")
+        self.assertTrue(len(response.stats) == 1,
+                        "Did not receive flow stats reply")
+        self.assertEquals(response.stats[0].flow_count, 0)
+        self.assertEquals(response.stats[0].packet_count, 0)
+        self.assertEquals(response.stats[0].byte_count, 0)
diff --git a/tests/load.py b/tests/load.py
index e913445..0f74d56 100644
--- a/tests/load.py
+++ b/tests/load.py
@@ -101,3 +101,100 @@
         logging.debug("Deleting all flows from switch")
         rc = delete_all_flows(self.controller)
         self.assertEqual(rc, 0, "Failed to delete all flows")
+
+class PacketInLoad(base_tests.SimpleDataPlane):
+    """
+    Generate lots of packet-in messages
+
+    Test packet-in function by sending lots of packets to the dataplane.
+    This test tracks the number of pkt-ins received but does not enforce
+    any requirements about the number received.
+    """
+    def runTest(self):
+        # Construct packet to send to dataplane
+        # Send packet to dataplane, once to each port
+        # Poll controller with expect message type packet in
+
+        rc = delete_all_flows(self.controller)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+        self.assertEqual(do_barrier(self.controller), 0, "Barrier failed")
+        out_count = 0
+        in_count = 0
+
+        of_ports = config["port_map"].keys()
+        for of_port in of_ports:
+            for pkt, pt in [
+               (simple_tcp_packet(), "simple TCP packet"),
+               (simple_tcp_packet(dl_vlan_enable=True,pktlen=108), 
+                "simple tagged TCP packet"),
+               (simple_eth_packet(), "simple Ethernet packet"),
+               (simple_eth_packet(pktlen=40), "tiny Ethernet packet")]:
+
+               logging.info("PKT IN test with %s, port %s" % (pt, of_port))
+               for count in range(100):
+                   out_count += 1
+                   self.dataplane.send(of_port, str(pkt))
+        time.sleep(2)
+        while True:
+            (response, raw) = self.controller.poll(ofp.OFPT_PACKET_IN,
+                                                   timeout=0)
+            if not response:
+                break
+            in_count += 1
+        logging.info("PacketInLoad Sent %d. Got %d." % (out_count, in_count))
+        
+
+
+class PacketOutLoad(base_tests.SimpleDataPlane):
+    """
+    Generate lots of packet-out messages
+
+    Test packet-out function by sending lots of packet-out msgs
+    to the switch.  This test tracks the number of packets received in 
+    the dataplane, but does not enforce any requirements about the 
+    number received.
+    """
+    def runTest(self):
+        # Construct packet to send to dataplane
+        # Send packet to dataplane
+        # Poll controller with expect message type packet in
+
+        rc = delete_all_flows(self.controller)
+        self.assertEqual(rc, 0, "Failed to delete all flows")
+
+        # These will get put into function
+        of_ports = config["port_map"].keys()
+        of_ports.sort()
+        out_count = 0
+        in_count = 0
+        xid = 100
+        for dp_port in of_ports:
+            for outpkt, opt in [
+               (simple_tcp_packet(), "simple TCP packet"),
+               (simple_eth_packet(), "simple Ethernet packet"),
+               (simple_eth_packet(pktlen=40), "tiny Ethernet packet")]:
+
+               logging.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')
+
+               logging.info("PacketOutLoad to: " + str(dp_port))
+               for count in range(100):
+                   msg.xid = xid
+                   xid += 1
+                   rv = self.controller.message_send(msg)
+                   self.assertTrue(rv == 0, "Error sending out message")
+                   out_count += 1
+
+               exp_pkt_arg = None
+               exp_port = None
+        time.sleep(2)
+        while True:
+            (of_port, pkt, pkt_time) = self.dataplane.poll(timeout=0)
+            if pkt is None:
+                break
+            in_count += 1
+        logging.info("PacketOutLoad Sent %d. Got %d." % (out_count, in_count))
diff --git a/tests/nicira_role.py b/tests/nicira_role.py
new file mode 100644
index 0000000..a8ac8cf
--- /dev/null
+++ b/tests/nicira_role.py
@@ -0,0 +1,46 @@
+"""
+"""
+import struct
+
+import logging
+
+from oftest import config
+import oftest.controller as controller
+import oftest.cstruct as ofp
+import oftest.message as message
+import oftest.base_tests as base_tests
+
+from oftest.testutils import *
+
+# Nicira vendor extension constants
+NXT_VENDOR = 0x00002320
+
+NXT_ROLE_REQUEST = 10
+
+NXT_ROLE_VALUE = dict( other=0, slave=1, master=2 )
+
+class NiciraRoleRequest(base_tests.SimpleDataPlane):
+    """
+    Exercise Nicira vendor extension for requesting HA roles
+    """
+
+    priority = 0
+
+    def nicira_role_request(self, role):
+        """
+        Use the BSN_SET_IP_MASK vendor command to change the IP mask for the
+        given wildcard index
+        """
+        logging.info("Sending role request %s" % role)
+        m = message.vendor()
+        m.vendor = NXT_VENDOR
+        m.data = struct.pack("!LL", NXT_ROLE_REQUEST, NXT_ROLE_VALUE[role])
+        return m
+
+    def runTest(self):
+        '''
+        For now, we only verify that a response is received.
+        '''
+        request = self.nicira_role_request("master")
+        response, pkt = self.controller.transact(request)
+        self.assertTrue(response is not None, "No reply to Nicira role request")
diff --git a/tests/pktact.py b/tests/pktact.py
index 10dbdb3..34654d1 100644
--- a/tests/pktact.py
+++ b/tests/pktact.py
@@ -16,6 +16,7 @@
 import logging
 import time
 import unittest
+import random
 
 from oftest import config
 import oftest.controller as controller
@@ -27,6 +28,8 @@
 import oftest.base_tests as base_tests
 import basic # for IterCases
 
+from oftest.parse import parse_mac, parse_ip
+
 from oftest.testutils import *
 
 WILDCARD_VALUES = [ofp.OFPFW_IN_PORT,
@@ -1931,5 +1934,794 @@
         testField("tp_src", 0xffff)
         testField("tp_dst", 0xffff)
 
+class DirectBadPacketBase(base_tests.SimpleDataPlane):
+    """
+    Base class for sending single packets with single flow table entries.
+    Used to verify matching of unusual packets and parsing/matching of 
+    corrupted packets.
+
+    The idea is to generate packets that may either be totally malformed or 
+    malformed just enough to trick the flow matcher into making mistakes.
+
+    Generate a 'bad' packet
+    Generate and install a matching flow
+    Add action to direct the packet to an egress port
+    Send the packet to ingress dataplane port
+    Verify the packet is received at the egress port only
+    """
+
+    RESULT_MATCH = "MATCH"
+    RESULT_NOMATCH = "NO MATCH"
+    RESULT_ANY = "ANY MATCH"
+    
+    def runTest(self):
+        pass
+        # TODO:
+        # - ICMP?
+        # - 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
+        fields = {
+            'dl_dst': ofp.OFPFW_DL_DST,
+            'dl_src': ofp.OFPFW_DL_SRC,
+            'dl_type': ofp.OFPFW_DL_TYPE,
+            'dl_vlan': ofp.OFPFW_DL_VLAN,
+            'nw_src': ofp.OFPFW_NW_SRC_MASK,
+            'nw_dst': ofp.OFPFW_NW_DST_MASK,
+            'nw_tos': ofp.OFPFW_NW_TOS,
+            'nw_proto': ofp.OFPFW_NW_PROTO,
+            'tp_src': ofp.OFPFW_TP_SRC,
+            'tp_dst': ofp.OFPFW_TP_DST,
+        }
+        for key in kwargs:
+            setattr(match, key, kwargs[key])
+            match.wildcards &= ~fields[key]
+        return match
+
+    def testPktsAgainstFlow(self, pkts, acts, match):
+        if type(acts) != list:
+            acts = [acts]
+        for info in pkts:
+            title, pkt, expected_result = info
+            self.testPktAgainstFlow(title, pkt, acts, match, expected_result)
+
+    def testPktAgainstFlow(self, title, pkt, acts, match, expected_result):
+        of_ports = config["port_map"].keys()
+        of_ports.sort()
+        self.assertTrue(len(of_ports) > 1, "Not enough ports for test")
+
+        rv = delete_all_flows(self.controller)
+        self.assertEqual(rv, 0, "Failed to delete all flows")
+
+        ingress_port = of_ports[0]
+        egress_port = of_ports[1]
+        
+        logging.info("Testing packet '%s', expect result %s" % 
+                       (title, expected_result))
+        logging.info("Ingress %s to egress %s" % 
+                       (str(ingress_port), str(egress_port)))
+        logging.info("Packet:")
+        logging.info(self.pktToStr(pkt))
+
+        match.in_port = ingress_port
+
+        request = message.flow_mod()
+        request.match = match
+        request.priority = 1
+
+        request.buffer_id = 0xffffffff
+        for act in acts:
+            act.port = egress_port
+            rv = request.actions.add(act)
+            self.assertTrue(rv, "Could not add action")
+
+        logging.info("Inserting flow")
+        rv = self.controller.message_send(request)
+        self.assertTrue(rv != -1, "Error installing flow mod")
+
+        # This flow speeds up negative tests
+        logging.info("Inserting catch-all flow")
+        request2 = message.flow_mod()
+        request2.match = self.createMatch()
+        request2.priority = 0
+        act = action.action_output()
+        act.port = ofp.OFPP_IN_PORT
+        request2.actions.add(act)
+        rv = self.controller.message_send(request2)
+        self.assertTrue(rv != -1, "Error installing flow mod")
+
+        self.assertEqual(do_barrier(self.controller), 0, "Barrier failed")
+
+        logging.info("Sending packet to dp port " + 
+                       str(ingress_port))
+        self.dataplane.send(ingress_port, str(pkt))
+
+        exp_pkt_arg = None
+        exp_port = None
+        if config["relax"]:
+            exp_pkt_arg = pkt
+            exp_port = egress_port
+
+        if expected_result == self.RESULT_MATCH:
+            timeout = -1 # default timeout
+        else:
+            timeout = 1 # short timeout for negative tests
+
+        (rcv_port, rcv_pkt, pkt_time) = self.dataplane.poll(exp_pkt=exp_pkt_arg,
+                                                            timeout=timeout)
+        if rcv_port == ingress_port:
+            logging.debug("Packet matched catch-all flow")
+            rcv_pkt = None
+
+        if expected_result == self.RESULT_MATCH:
+            self.assertTrue(rcv_pkt is not None, 
+                            "Did not receive packet, expected a match")
+            logging.debug("Packet len " + str(len(rcv_pkt)) + " in on " + 
+                          str(rcv_port))
+            self.assertEqual(rcv_port, egress_port, "Unexpected receive port")
+            str_pkt = str(pkt)
+            str_rcv_pkt = str(rcv_pkt)
+            str_rcv_pkt = str_rcv_pkt[0:len(str_pkt)]
+            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)))
+            self.assertEqual(str_pkt, str_rcv_pkt,
+                             'Response packet does not match send packet')
+        elif expected_result == self.RESULT_NOMATCH:
+            self.assertTrue(rcv_pkt is None, "Received packet, expected drop")
+        else:
+            logging.debug("Match or drop accepted. Result = %s" %
+                            ("match" if rcv_pkt is not None else "drop"))
+
+
+class DirectBadIpTcpPacketsBase(DirectBadPacketBase):
+    """
+    Base class for TCP and UDP parsing/matching verification under corruptions
+    """
+    def runTest(self):
+        pass
+
+    def runTestWithProto(self, protoName = 'TCP'):
+        dl_dst='00:01:02:03:04:05'
+        dl_src='00:06:07:08:09:0a'
+        ip_src='192.168.0.1'
+        ip_dst='192.168.0.2'
+        ip_tos=0
+        tcp_sport=1234
+        tcp_dport=80
+        
+        # Generate a proper packet for constructing a match
+        tp = None
+        if protoName == 'TCP':
+            tp = scapy.TCP
+            proto = 6
+        elif protoName == 'UDP':
+            tp = scapy.UDP
+            proto = 17
+        else:
+            raise Exception("Passed in unknown proto name")
+
+        match_pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
+            scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos)/ \
+            tp(sport=tcp_sport, dport=tcp_dport)
+        match = packet_to_flow_match(self, match_pkt)
+        self.assertTrue(match is not None, 
+                        "Could not generate flow match from pkt")
+        match.wildcards &= ~ofp.OFPFW_IN_PORT
+        
+        def testPacket(title, pkt, result):
+            act = action.action_output()
+            pkts = [
+                [title, pkt, result]
+            ]
+            self.testPktsAgainstFlow(pkts, act, match)
+        
+        # Try incomplete IP headers
+        testPacket("Incomplete IP header (1 bytes)",
+            scapy.Ether(dst=dl_dst, src=dl_src)/ \
+                str(scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, proto=proto))[0:1],
+            self.RESULT_NOMATCH,
+        )
+        testPacket("Incomplete IP header (2 bytes)",
+            scapy.Ether(dst=dl_dst, src=dl_src)/ \
+                str(scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, proto=proto))[0:2],
+            self.RESULT_NOMATCH,
+        )
+        testPacket("Incomplete IP header (3 bytes)",
+            scapy.Ether(dst=dl_dst, src=dl_src)/ \
+                str(scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, proto=proto))[0:3],
+            self.RESULT_NOMATCH,
+        )
+        testPacket("Incomplete IP header (12 bytes)",
+            scapy.Ether(dst=dl_dst, src=dl_src)/ \
+                str(scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, proto=proto))[0:12],
+            self.RESULT_NOMATCH,
+        )
+        testPacket("Incomplete IP header (16 bytes)",
+            scapy.Ether(dst=dl_dst, src=dl_src)/ \
+                str(scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, proto=proto))[0:16],
+            self.RESULT_NOMATCH,
+        )
+        testPacket("Incomplete IP header (19 bytes)",
+            scapy.Ether(dst=dl_dst, src=dl_src)/ \
+                str(scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, proto=proto))[0:19],
+            self.RESULT_NOMATCH,
+        )
+            
+        # Try variations where the TCP header is missing or incomplete. As we 
+        # saw bugs before where buffers were reused and lengths weren't honored,
+        # we initiatlize once with a non-matching full packet and once with a 
+        # matching full packet.
+        testPacket("Non-Matching TCP packet, warming buffer",
+            scapy.Ether(dst=dl_dst, src=dl_src)/ \
+                scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, proto=proto)/ \
+                tp(sport=tcp_sport, dport=tcp_dport + 1),
+            self.RESULT_NOMATCH,
+        )
+        testPacket("Missing TCP header, buffer warmed with non-match",
+            scapy.Ether(dst=dl_dst, src=dl_src)/ \
+                scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, proto=proto),
+            self.RESULT_NOMATCH,
+        )
+        testPacket("Matching TCP packet, warming buffer",
+            scapy.Ether(dst=dl_dst, src=dl_src)/ \
+                scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, proto=proto)/ \
+                tp(sport=tcp_sport, dport=tcp_dport),
+            self.RESULT_MATCH,
+        )
+        testPacket("Missing TCP header, buffer warmed with match",
+            scapy.Ether(dst=dl_dst, src=dl_src)/ \
+                scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, proto=proto),
+            self.RESULT_NOMATCH,
+        )
+        testPacket("Truncated TCP header: 2 bytes",
+            scapy.Ether(dst=dl_dst, src=dl_src)/ \
+                scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, proto=proto)/ \
+                (str(tp(sport=tcp_sport, dport=tcp_dport))[0:2]),
+            self.RESULT_NOMATCH,
+        )
+            
+        # Play with IP header length values that put the start of TCP either
+        # inside the generated TCP header or beyond. In some cases it may even
+        # be beyond the packet boundary. Also play with IP options and more 
+        # importantly IP total length corruptions.
+        testPacket("TCP packet, corrupt ihl (0x6)",
+            simple_tcp_packet(ip_ihl=6),
+            self.RESULT_NOMATCH,
+        )
+        testPacket("TCP packet, corrupt ihl (0xf)",
+            simple_tcp_packet(ip_ihl=0xf), # ihl = 15 * 4 = 60
+            self.RESULT_NOMATCH,
+        )
+        testPacket("TCP packet, corrupt ihl and total length",
+            simple_tcp_packet(ip_ihl=0xf, pktlen=56), # ihl = 15 * 4 = 60,
+            self.RESULT_NOMATCH,
+        )
+        testPacket("Corrupt IPoption: First 4 bytes of matching TCP header",
+            simple_tcp_packet(
+                ip_options=scapy.IPOption('\x04\xd2\x00\x50'), 
+                tcp_dport=2, tcp_sport=2
+            ),
+            self.RESULT_NOMATCH,
+        )
+        testPacket("Missing TCP header, corrupt ihl",
+            scapy.Ether(dst=dl_dst, src=dl_src)/ \
+                scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ihl=0xf, proto=proto),
+            self.RESULT_NOMATCH,
+        )
+        testPacket("Missing TCP header, corrupt total length",
+            scapy.Ether(dst=dl_dst, src=dl_src)/ \
+                scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, proto=proto, len= 100),
+            self.RESULT_NOMATCH,
+        )
+        testPacket("Missing TCP header, corrupt ihl and total length",
+            scapy.Ether(dst=dl_dst, src=dl_src)/ \
+                scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ihl=0xf, proto=proto, len=43),
+            self.RESULT_NOMATCH,
+        )
+        testPacket("Incomplete IP header (12 bytes), corrupt ihl and total length",
+            scapy.Ether(dst=dl_dst, src=dl_src)/ \
+                str(scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, proto=proto, ihl=10, len=43))[0:12],
+            self.RESULT_NOMATCH,
+        )
+        testPacket("Incomplete IP header (16 bytes), corrupt ihl and total length",
+            scapy.Ether(dst=dl_dst, src=dl_src)/ \
+                str(scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, proto=proto, ihl=10, len=43))[0:16],
+            self.RESULT_NOMATCH,
+        )
+            
+        # Try an incomplete TCP header that has enough bytes to carry source and
+        # destination ports. As that is all we care about during matching, some
+        # implementations may match and some may drop the packet
+        testPacket("Incomplete TCP header: src/dst port present",
+            scapy.Ether(dst=dl_dst, src=dl_src)/ \
+                scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, proto=proto)/ \
+                (str(tp(sport=tcp_sport, dport=tcp_dport))[0:4]),
+            self.RESULT_ANY,
+        )
+
+        for i in range(1):
+            for length in range(40 / 4): # IPv4 options are a maximum of 40 in length
+                bytes = "".join([("%c" % random.randint(0, 255)) for x in range(length * 4)])
+                eth = scapy.Ether(dst=dl_dst, src=dl_src)
+                ip = scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ihl=5 + length, proto=proto)
+                tcp = tp(sport=tcp_sport, dport=tcp_dport+1)
+                pkt = eth / ip
+                pkt = pkt / bytes
+                pkt = pkt / str(tcp)
+                testPacket("Random IP options len = %d - TP match must fail" % length * 4, 
+                    pkt, 
+                    self.RESULT_NOMATCH
+                )
+
+                eth = scapy.Ether(dst=dl_dst, src=dl_src)
+                ip = scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ihl=5 + length, proto=proto)
+                tcp = tp(sport=tcp_sport, dport=tcp_dport)
+                pkt = eth / ip
+                pkt = pkt / bytes
+                pkt = pkt / str(tcp)
+
+                testPacket("Random IP options len = %d - May match", 
+                    pkt, 
+                    self.RESULT_ANY
+                )
+        
+
+class DirectBadIpTcpPackets(DirectBadIpTcpPacketsBase):
+    """
+    Verify IP/TCP parsing and matching. Focus on packet corruptions 
+    """
+    def runTest(self):
+        self.runTestWithProto(protoName = 'TCP')
+
+class DirectBadIpUdpPackets(DirectBadIpTcpPacketsBase):
+    """
+    Verify IP/UDP parsing and matching. Focus on packet corruptions 
+    """
+    def runTest(self):
+        self.runTestWithProto(protoName = 'UDP')
+
+class DirectBadLlcPackets(DirectBadPacketBase):
+    """
+    Verify LLC/SNAP parsing and matching. Focus on packet corruptions 
+    """
+    def runTest(self):
+        dl_dst='00:01:02:03:04:05'
+        dl_src='00:06:07:08:09:0a'
+        ip_src='192.168.0.1'
+        ip_dst='192.168.0.2'
+        ip_tos=0
+        tcp_sport=1234
+        tcp_dport=80
+
+        IS_SNAP_IP = 1
+        IS_SNAP_IP_CORRUPT = 2
+        IS_NOT_SNAP_IP = 3
+
+        def testPacketTcpMatch(title, llc):
+            match_pkt = scapy.Ether(dst=dl_dst, src=dl_src)/ \
+                scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos)/ \
+                scapy.TCP(sport=tcp_sport, dport=tcp_dport)
+            match = packet_to_flow_match(self, match_pkt)
+            self.assertTrue(match is not None, 
+                            "Could not generate flow match from pkt")
+            match.wildcards &= ~ofp.OFPFW_IN_PORT
+            act = action.action_output()
+            
+            self.testPktsAgainstFlow(
+                [[
+                    "TCP match - LLC frame correct length - %s" % title,
+                    scapy.Ether(dst=dl_dst, src=dl_src, type=len(llc)) / llc,
+                    self.RESULT_ANY,
+                ]],
+                act, match
+            )
+    
+            # Corrupt length field
+            ethLen = random.randint(0, 1535)
+            self.testPktsAgainstFlow(
+                [[
+                    "TCP match - LLC frame corrupted length - %s" % title,
+                    scapy.Ether(dst=dl_dst, src=dl_src, type=ethLen) / llc,
+                    self.RESULT_ANY,
+                ]],
+                act, match
+            )
+
+        def testPacketEthSrcDstMatch(title, llc):
+            # Matching based on Ethernet source and destination
+            match_pkt = scapy.Ether(dst=dl_dst, src=dl_src)
+            match = packet_to_flow_match(self, match_pkt)
+            self.assertTrue(match is not None, 
+                            "Could not generate flow match from pkt")
+            match.wildcards &= ~ofp.OFPFW_IN_PORT
+            match.wildcards |= ofp.OFPFW_DL_TYPE
+            self.testPktsAgainstFlow(
+                [[
+                    "Eth addr match - LLC frame correct length- %s" % title,
+                    scapy.Ether(dst=dl_dst, src=dl_src, type=len(llc)) / llc,
+                    self.RESULT_MATCH,
+                ]],
+                action.action_output(), match
+            )
+    
+            # Corrupt length field
+            ethLen = random.randint(0, 1535)
+            self.testPktsAgainstFlow(
+                [[
+                    "Eth addr match - LLC frame corrupted length- %s" % title,
+                    scapy.Ether(dst=dl_dst, src=dl_src, type=ethLen) / llc,
+                    self.RESULT_ANY,
+                ]],
+                action.action_output(), match
+            )
+            
+        def testPacketEthSrcDstTypeMatch(title, llc, is_snap_ip):
+            # Matching based on Ethernet source, destination and type
+            match_pkt = scapy.Ether(dst=dl_dst, src=dl_src, type=0x800)
+            match = packet_to_flow_match(self, match_pkt)
+            self.assertTrue(match is not None, 
+                            "Could not generate flow match from pkt")
+            match.wildcards &= ~ofp.OFPFW_IN_PORT
+            if is_snap_ip == IS_SNAP_IP:
+                is_match = self.RESULT_MATCH
+            elif is_snap_ip == IS_SNAP_IP_CORRUPT:
+                is_match = self.RESULT_ANY
+            else:
+                is_match = self.RESULT_NOMATCH
+            self.testPktsAgainstFlow(
+                [[
+                    "Eth addr+type match - LLC frame correct length - %s" % title,
+                    scapy.Ether(dst=dl_dst, src=dl_src, type=len(llc)) / llc,
+                    is_match,
+                ]],
+                action.action_output(), match
+            )
+    
+            # Corrupt length field
+            ethLen = random.randint(0, 1535)
+            self.testPktsAgainstFlow(
+                [[
+                    "Eth addr+type match - LLC frame corrupted length - %s" % title,
+                    scapy.Ether(dst=dl_dst, src=dl_src, type=ethLen) / llc,
+                    self.RESULT_ANY,
+                ]],
+                action.action_output(), match
+            )
+
+        def testPacket(title, llc, is_snap_ip):
+            testPacketTcpMatch(title, llc)
+            testPacketEthSrcDstMatch(title, llc)
+            testPacketEthSrcDstTypeMatch(title, llc, is_snap_ip)
+
+        testPacket("LLC - No SNAP - No Payload",
+            scapy.LLC(dsap=0x33, ssap=0x44, ctrl=0x03),
+            IS_NOT_SNAP_IP,
+        )
+        testPacket("LLC - No SNAP - Small Payload",
+            scapy.LLC(dsap=0x33, ssap=0x44, ctrl=0x03) / ("S" * 10),
+            IS_NOT_SNAP_IP,
+        )
+        testPacket("LLC - No SNAP - Max -1 Payload",
+            scapy.LLC(dsap=0x33, ssap=0x44, ctrl=0x03) / ("S" * (1500 - 3 - 1)),
+            IS_NOT_SNAP_IP,
+        )
+        testPacket("LLC - No SNAP - Max Payload",
+            scapy.LLC(dsap=0x33, ssap=0x44, ctrl=0x03) / ("S" * (1500 - 3)),
+            IS_NOT_SNAP_IP,
+        )
+        testPacket("LLC - SNAP - Small bogus payload",
+            scapy.LLC(dsap=0xaa, ssap=0xaa, ctrl=0x03)/ \
+                scapy.SNAP(OUI=0x000000, code=0x800) / ("S" * 10),
+            IS_SNAP_IP_CORRUPT,
+        )
+        testPacket("LLC - SNAP - Max -1 bogus payload",
+            scapy.LLC(dsap=0xaa, ssap=0xaa, ctrl=0x03)/ \
+                scapy.SNAP(OUI=0x000000, code=0x3) / ("S" * (1500 - 3 - 5 - 1)),
+            IS_NOT_SNAP_IP,
+        )
+        testPacket("LLC - SNAP - Max bogus payload",
+            scapy.LLC(dsap=0xaa, ssap=0xaa, ctrl=0x03)/ \
+                scapy.SNAP(OUI=0x000000, code=0x3) / ("S" * (1500 - 3 - 5)),
+            IS_NOT_SNAP_IP,
+        )
+        testPacket("LLC - SNAP - IP - TCP",
+            scapy.LLC(dsap=0xaa, ssap=0xaa, ctrl=0x03)/ \
+                scapy.SNAP(OUI=0x000000, code=0x800)/ \
+                scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, proto=6)/ \
+                scapy.TCP(sport=tcp_sport, dport=tcp_dport),
+            IS_SNAP_IP,
+        )
+        
+
+class DirectLlcPackets(DirectBadPacketBase):
+    """
+    Verify LLC/SNAP parsing (valid and corrupted packets) and matching
+    """
+    def runTest(self):
+        dl_dst='00:01:02:03:04:05'
+        dl_src='00:06:07:08:09:0a'
+        ip_src='192.168.0.1'
+        ip_dst='192.168.0.2'
+        ip_tos=0
+        tcp_sport=1234
+        tcp_dport=80
+
+        # Test ethertype in face of LLC/SNAP and OFP_DL_TYPE_NOT_ETH_TYPE
+        IS_SNAP_NOT_IP = 1
+        IS_SNAP_AND_IP = 2
+        IS_NOT_SNAP = 3
+
+        def testPacketEthTypeIP(title, llc, is_snap):
+            match_pkt = scapy.Ether(dst=dl_dst, src=dl_src, type=0x800)
+            match = packet_to_flow_match(self, match_pkt)
+            self.assertTrue(match is not None, 
+                            "Could not generate flow match from pkt")
+            match.wildcards &= ~ofp.OFPFW_IN_PORT
+            pkts = []
+            if is_snap == IS_NOT_SNAP or is_snap == IS_SNAP_NOT_IP:
+                result = self.RESULT_NOMATCH
+            else:
+                result = self.RESULT_MATCH
+            pkts.append([
+                "Ether type 0x800 match - %s" % title,
+                scapy.Ether(dst=dl_dst, src=dl_src, type=len(llc)) / llc,
+                result,
+            ])
+            act = action.action_output()
+            self.testPktsAgainstFlow(pkts, act, match)
+    
+        def testPacketEthTypeNotEth(title, llc, is_snap):
+            match_pkt = scapy.Ether(dst = dl_dst, src = dl_src, 
+                                    type = ofp.OFP_DL_TYPE_NOT_ETH_TYPE)
+            match = packet_to_flow_match(self, match_pkt)
+            self.assertTrue(match is not None, 
+                            "Could not generate flow match from pkt")
+            match.wildcards &= ~ofp.OFPFW_IN_PORT
+            pkts = []
+            if is_snap == IS_NOT_SNAP:
+                result = self.RESULT_MATCH
+            else:
+                result = self.RESULT_NOMATCH
+            pkts.append([
+                "Ether type OFP_DL_TYPE_NOT_ETH_TYPE match - %s" % title,
+                scapy.Ether(dst=dl_dst, src=dl_src, type=len(llc)) / llc,
+                result,
+            ])
+            act = action.action_output()
+            self.testPktsAgainstFlow(pkts, act, match)
+    
+        def testPacket(title, llc, is_snap):
+            testPacketEthTypeIP(title, llc, is_snap)
+            testPacketEthTypeNotEth(title, llc, is_snap)
+        
+        testPacket("LLC - No SNAP - No Payload",
+            scapy.LLC(dsap=0x33, ssap=0x44, ctrl=0x03),
+            IS_NOT_SNAP,
+        )
+        testPacket("LLC (with information field) - No SNAP - No Payload",
+            scapy.LLC(dsap=0x33, ssap=0x44, ctrl=0x12) / "S",
+            IS_NOT_SNAP,
+        )
+        testPacket("LLC - No SNAP - Small Payload",
+            scapy.LLC(dsap=0x33, ssap=0x44, ctrl=0x03) / ("S" * 10),
+            IS_NOT_SNAP,
+        )
+        testPacket("LLC - No SNAP - Max -1 Payload",
+            scapy.LLC(dsap=0x33, ssap=0x44, ctrl=0x03) / ("S" * (1500 - 3 - 1)),
+            IS_NOT_SNAP,
+        )
+        testPacket("LLC - No SNAP - Max Payload",
+            scapy.LLC(dsap=0x33, ssap=0x44, ctrl=0x03) / ("S" * (1500 - 3)),
+            IS_NOT_SNAP,
+        )
+        testPacket("LLC - SNAP - Non-default OUI",
+            scapy.LLC(dsap=0xaa, ssap=0xaa, ctrl=0x03)/ \
+                scapy.SNAP(OUI=0x000001, code=0x800) / ("S" * 10),
+            IS_NOT_SNAP,
+        )
+        testPacket("LLC - SNAP - Default OUI",
+            scapy.LLC(dsap=0xaa, ssap=0xaa, ctrl=0x03)/ \
+                scapy.SNAP(OUI=0x000000, code=0x800) / ("S" * 10),
+            IS_SNAP_AND_IP,
+        )
+        testPacket("LLC - SNAP - Max -1 bogus payload",
+            scapy.LLC(dsap=0xaa, ssap=0xaa, ctrl=0x03)/ \
+                scapy.SNAP(OUI=0x000000, code=0x3) / ("S" * (1500 - 3 - 5 - 1)),
+            IS_SNAP_NOT_IP,
+        )
+        testPacket("LLC - SNAP - Max bogus payload",
+            scapy.LLC(dsap=0xaa, ssap=0xaa, ctrl=0x03)/ \
+                scapy.SNAP(OUI=0x000000, code=0x3) / ("S" * (1500 - 3 - 5)),
+            IS_SNAP_NOT_IP,
+        )
+        testPacket("LLC - SNAP - IP - TCP",
+            scapy.LLC(dsap=0xaa, ssap=0xaa, ctrl=0x03)/ \
+                scapy.SNAP(OUI=0x000000, code=0x800)/ \
+                scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, proto=6)/ \
+                scapy.TCP(sport=tcp_sport, dport=tcp_dport),
+            IS_SNAP_AND_IP,
+        )
+
+
+class DirectArpPackets(DirectBadPacketBase):
+    """
+    Verify ARP parsing (valid and corrupted packets) and ARP matching
+    """
+    def runTest(self):
+        self.testArpHandling()
+
+    def testArpHandling(self):
+        dl_dst='00:01:02:03:04:05'
+        dl_src='00:06:07:08:09:0a'
+        ip_src='192.168.0.1'
+        ip_dst='192.168.0.2'
+        ip_src2='192.168.1.1'
+        ip_dst2='192.168.1.2'
+        ip_tos=0
+        tcp_sport=1234
+        tcp_dport=80
+
+        def testPacket(title, arp_match, arp_pkt, result):
+            pkts = []
+    
+            match_pkt = scapy.Ether(dst=dl_dst, src=dl_src) / arp_match
+            match = packet_to_flow_match(self, match_pkt)
+            self.assertTrue(match is not None, 
+                            "Could not generate flow match from pkt")
+            match.wildcards &= ~ofp.OFPFW_IN_PORT
+            
+            pkts.append([
+                title,
+                scapy.Ether(dst=dl_dst, src=dl_src) / arp_pkt,
+                result,
+            ])
+    
+            act = action.action_output()
+            self.testPktsAgainstFlow(pkts, act, match)
+            
+        testPacket("Basic ARP",
+            scapy.ARP(psrc=ip_src, pdst=ip_dst, op = 1),
+            scapy.ARP(hwdst = '00:00:00:00:00:00', hwsrc = dl_src,
+                      psrc = ip_src, pdst = ip_dst, hwlen = 6, plen = 4,
+                      ptype = 0x800, hwtype = 1, op = 1),
+            self.RESULT_MATCH
+        )
+        # More stuff:
+        # - Non matches on any property
+        # - Corrupted hwlen and plen
+        # - Other hwtype, ptype
+        # - Truncated ARP pkt
+
+    
+class DirectVlanPackets(DirectBadPacketBase):
+    """
+    Verify VLAN parsing (valid and corrupted packets) and ARP matching
+    """
+    def runTest(self):
+        dl_dst='00:01:02:03:04:05'
+        dl_src='00:06:07:08:09:0a'
+        ip_src='192.168.0.1'
+        ip_dst='192.168.0.2'
+        ip_src2='192.168.1.1'
+        ip_dst2='192.168.1.2'
+        ip_tos=0
+        tcp_sport=1234
+        tcp_dport=80
+
+        def testPacket(title, match, pkt, result):
+            pkts = []
+    
+            self.assertTrue(match is not None, 
+                            "Could not generate flow match from pkt")
+            match.wildcards &= ~ofp.OFPFW_IN_PORT
+            
+            pkts.append([
+                "%s" % title,
+                pkt,
+                result,
+            ])
+    
+            act = action.action_output()
+            self.testPktsAgainstFlow(pkts, act, match)
+
+        testPacket("Basic MAC matching - IPv4 payload",
+            self.createMatch(dl_dst=parse_mac(dl_dst), dl_src=parse_mac(dl_src)),
+            scapy.Ether(dst=dl_dst, src=dl_src, type=0x800) / scapy.IP(),
+            self.RESULT_MATCH
+        )
+        testPacket("Basic MAC matching - VMware beacon - no payload",
+            self.createMatch(dl_dst=parse_mac(dl_dst), dl_src=parse_mac(dl_src)),
+            scapy.Ether(dst=dl_dst, src=dl_src, type=0x8922),
+            self.RESULT_MATCH
+        )
+        testPacket("Basic MAC matching - VMware beacon - with payload",
+            self.createMatch(dl_dst=parse_mac(dl_dst), dl_src=parse_mac(dl_src)),
+            scapy.Ether(dst=dl_dst, src=dl_src, type=0x8922)/ ("X" * 1),
+            self.RESULT_MATCH
+        )
+        testPacket("Basic MAC matching - IPv6 payload",
+            self.createMatch(dl_dst=parse_mac(dl_dst), dl_src=parse_mac(dl_src)),
+            scapy.Ether(dst=dl_dst, src=dl_src) / scapy.IPv6(),
+            self.RESULT_MATCH
+        )
+        testPacket("Basic MAC matching with VLAN tag present",
+            self.createMatch(dl_dst=parse_mac(dl_dst), dl_src=parse_mac(dl_src)),
+            scapy.Ether(dst=dl_dst, src=dl_src)/ \
+                scapy.Dot1Q(prio=5, vlan=1000)/ \
+                scapy.IP(),
+            self.RESULT_MATCH
+        )
+        testPacket("Basic MAC matching with VLAN tag present",
+            self.createMatch(dl_dst=parse_mac(dl_dst), dl_src=parse_mac(dl_src),
+                             dl_type=0x800),
+            scapy.Ether(dst=dl_dst, src=dl_src)/ \
+                scapy.Dot1Q(prio=5, vlan=1000)/ \
+                scapy.IP(),
+            self.RESULT_MATCH
+        )
+        testPacket("Ether matching with VLAN tag present - No type match",
+            self.createMatch(dl_dst=parse_mac(dl_dst), dl_src=parse_mac(dl_src),
+                             dl_type=0x801),
+            scapy.Ether(dst=dl_dst, src=dl_src)/ \
+                scapy.Dot1Q(prio=5, vlan=1000)/ \
+                scapy.IP(),
+            self.RESULT_NOMATCH
+        )
+        testPacket("Ether matching with VLAN tag present - No type match 0x8100",
+            self.createMatch(dl_dst=parse_mac(dl_dst), dl_src=parse_mac(dl_src),
+                             dl_type=0x8100),
+            scapy.Ether(dst=dl_dst, src=dl_src)/ \
+                scapy.Dot1Q(prio=5, vlan=1000)/ \
+                scapy.IP(),
+            self.RESULT_NOMATCH
+        )
+        testPacket("Ether matching with double VLAN tag - Wrong type match",
+            self.createMatch(dl_dst=parse_mac(dl_dst), dl_src=parse_mac(dl_src),
+                             dl_type=0x800),
+            scapy.Ether(dst=dl_dst, src=dl_src)/ \
+                scapy.Dot1Q(prio=5, vlan=1000)/ \
+                scapy.Dot1Q(prio=3, vlan=1005)/ \
+                scapy.IP(),
+            self.RESULT_NOMATCH
+        )
+        testPacket("Ether matching with double VLAN tag - Type match",
+            self.createMatch(dl_dst=parse_mac(dl_dst), dl_src=parse_mac(dl_src),
+                             dl_type=0x8100),
+            scapy.Ether(dst=dl_dst, src=dl_src)/ \
+                scapy.Dot1Q(prio=5, vlan=1000)/ \
+                scapy.Dot1Q(prio=3, vlan=1005)/ \
+                scapy.IP(),
+            self.RESULT_MATCH
+        )
+        testPacket("IP matching - VLAN tag",
+            self.createMatch(dl_dst=parse_mac(dl_dst), dl_src=parse_mac(dl_src),
+                             dl_type=0x0800,
+                             nw_src=parse_ip(ip_src), nw_dst=parse_ip(ip_dst)),
+            scapy.Ether(dst=dl_dst, src=dl_src)/ \
+                scapy.Dot1Q(prio=5, vlan=1000)/ \
+                scapy.IP(src=ip_src, dst=ip_dst),
+            self.RESULT_MATCH
+        )
+        # XXX:
+        # - Matching on VLAN ID and Prio
+        # - Actions
+
+    
+
 if __name__ == "__main__":
     print "Please run through oft script:  ./oft --test_spec=basic"