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"