From 13b3c2bc30eed848a828eb96bdf998829a66f294 Mon Sep 17 00:00:00 2001 From: Rob Sherwood Date: Tue, 21 Jul 2015 20:08:01 -0700 Subject: [PATCH 01/12] Initial testing for OF-DPA 2.0 OF-DPA has a custom hardware pipeline and most OFTest tests assume that any table will accept any match (which is not true!). Trying to initially port basic.PacketInExact. Many pure control plane tests work out of the box. Lots of non-working tests. FYI: `./oft -V1.3 --test-dir=tests-ofdpa-2.0` to run all of the OFDPA tests --- tests-ofdpa-2.0/basic.py | 570 +++++++++++++++++++++++++++++++++ tests-ofdpa-2.0/ofdpa_utils.py | 70 ++++ 2 files changed, 640 insertions(+) create mode 100644 tests-ofdpa-2.0/basic.py create mode 100644 tests-ofdpa-2.0/ofdpa_utils.py diff --git a/tests-ofdpa-2.0/basic.py b/tests-ofdpa-2.0/basic.py new file mode 100644 index 000000000..fc1d95d9f --- /dev/null +++ b/tests-ofdpa-2.0/basic.py @@ -0,0 +1,570 @@ +# Distributed under the OpenFlow Software License (see LICENSE) +# Copyright (c) 2010 The Board of Trustees of The Leland Stanford Junior University +# Copyright (c) 2012, 2013 Big Switch Networks, Inc. +# Copyright (c) 2012, 2013 CPqD +# Copyright (c) 2012, 2013 Ericsson +""" +Basic test cases + +Test cases in other modules depend on this functionality. +""" + +import logging + +from oftest import config +import oftest.base_tests as base_tests +import ofp +import ofdpa_utils + +from oftest.testutils import * + +@group('smoke') +class Echo(base_tests.SimpleProtocol): + """ + Test echo response with no data + """ + def runTest(self): + request = ofp.message.echo_request() + response, pkt = self.controller.transact(request) + self.assertTrue(response is not None, + "Did not get echo reply") + self.assertEqual(response.type, ofp.OFPT_ECHO_REPLY, + 'response is not echo_reply') + self.assertEqual(request.xid, response.xid, + 'response xid != request xid') + self.assertEqual(len(response.data), 0, 'response data non-empty') + +class EchoWithData(base_tests.SimpleProtocol): + """ + Test echo response with short string data + """ + def runTest(self): + data = 'OpenFlow Will Rule The World' + request = ofp.message.echo_request(data=data) + response, _ = self.controller.transact(request) + self.assertTrue(response is not None, + "Did not get echo reply") + self.assertEqual(response.type, ofp.OFPT_ECHO_REPLY, + 'response is not echo_reply') + self.assertEqual(request.xid, response.xid, + 'response xid != request xid') + self.assertEqual(request.data, response.data, + 'response data != request data') + +class FeaturesRequest(base_tests.SimpleProtocol): + """ + Test features_request to make sure we get a response + + Does NOT test the contents; just that we get a response + """ + def runTest(self): + request = ofp.message.features_request() + response,_ = self.controller.transact(request) + self.assertTrue(response is not None, + 'Did not get features reply') + +class DefaultDrop(base_tests.SimpleDataPlane): + """ + Check that an empty flowtable results in drops + """ + def runTest(self): + in_port, = openflow_ports(1) + delete_all_flows(self.controller) + + pkt = str(simple_tcp_packet()) + self.dataplane.send(in_port, pkt) + verify_no_packet_in(self, pkt, None) + verify_packets(self, pkt, []) + +class OutputExact(base_tests.SimpleDataPlane): + """ + Test output function for an exact-match flow + + For each port A, adds a flow directing matching packets to that port. + Then, for all other ports B != A, verifies that sending a matching packet + to B results in an output to A. + """ + def runTest(self): + ports = sorted(config["port_map"].keys()) + + delete_all_flows(self.controller) + + parsed_pkt = simple_tcp_packet() + pkt = str(parsed_pkt) + match = packet_to_flow_match(self, parsed_pkt) + + for out_port in ports: + request = ofp.message.flow_add( + table_id=test_param_get("table", 0), + cookie=42, + match=match, + instructions=[ + ofp.instruction.apply_actions( + actions=[ + ofp.action.output( + port=out_port, + max_len=ofp.OFPCML_NO_BUFFER)])], + buffer_id=ofp.OFP_NO_BUFFER, + priority=1000) + + logging.info("Inserting flow sending matching packets to port %d", out_port) + self.controller.message_send(request) + do_barrier(self.controller) + + for in_port in ports: + if in_port == out_port: + continue + logging.info("OutputExact test, ports %d to %d", in_port, out_port) + self.dataplane.send(in_port, pkt) + verify_packets(self, pkt, [out_port]) + +class OutputWildcard(base_tests.SimpleDataPlane): + """ + Test output function for a match-all (but not table-miss) flow + + For each port A, adds a flow directing all packets to that port. + Then, for all other ports B != A, verifies that sending a packet + to B results in an output to A. + """ + def runTest(self): + ports = sorted(config["port_map"].keys()) + + delete_all_flows(self.controller) + + pkt = str(simple_tcp_packet()) + + for out_port in ports: + request = ofp.message.flow_add( + table_id=test_param_get("table", 0), + cookie=42, + instructions=[ + ofp.instruction.apply_actions( + actions=[ + ofp.action.output( + port=out_port, + max_len=ofp.OFPCML_NO_BUFFER)])], + buffer_id=ofp.OFP_NO_BUFFER, + priority=1000) + + logging.info("Inserting flow sending all packets to port %d", out_port) + self.controller.message_send(request) + do_barrier(self.controller) + + for in_port in ports: + if in_port == out_port: + continue + logging.info("OutputWildcard test, ports %d to %d", in_port, out_port) + self.dataplane.send(in_port, pkt) + verify_packets(self, pkt, [out_port]) + +class PacketInExact(base_tests.SimpleDataPlane): + """ + Test packet in function for an exact-match flow + + Send a packet to each dataplane port and verify that a packet + in message is received from the controller for each + """ + def runTest(self): + delete_all_flows(self.controller) + + # required for OF-DPA to not drop packets + ofdpa_utils.installDefaultVlan(self.controller) + + parsed_pkt = simple_tcp_packet() + pkt = str(parsed_pkt) + match = packet_to_flow_match(self, parsed_pkt) + match.vlan_vid = 1 + + request = ofp.message.flow_add( + table_id=ofdpa_utils.ACL_TABLE.table_id, + cookie=42, + match=match, + instructions=[ + ofp.instruction.apply_actions( + actions=[ + ofp.action.output( + port=ofp.OFPP_CONTROLLER, + max_len=ofp.OFPCML_NO_BUFFER)])], + buffer_id=ofp.OFP_NO_BUFFER, + priority=1000) + + logging.info("Inserting flow sending matching packets to controller") + self.controller.message_send(request) + do_barrier(self.controller) + + for of_port in config["port_map"].keys(): + logging.info("PacketInExact test, port %d", of_port) + self.dataplane.send(of_port, pkt) + verify_packet_in(self, pkt, of_port, ofp.OFPR_ACTION) + verify_packets(self, pkt, []) + +class PacketInWildcard(base_tests.SimpleDataPlane): + """ + Test packet in function for a match-all flow + + Send a packet to each dataplane port and verify that a packet + in message is received from the controller for each + """ + def runTest(self): + delete_all_flows(self.controller) + + pkt = str(simple_tcp_packet()) + + request = ofp.message.flow_add( + table_id=test_param_get("table", 0), + cookie=42, + instructions=[ + ofp.instruction.apply_actions( + actions=[ + ofp.action.output( + port=ofp.OFPP_CONTROLLER, + max_len=ofp.OFPCML_NO_BUFFER)])], + buffer_id=ofp.OFP_NO_BUFFER, + priority=1000) + + logging.info("Inserting flow sending all packets to controller") + self.controller.message_send(request) + do_barrier(self.controller) + + for of_port in config["port_map"].keys(): + logging.info("PacketInWildcard test, port %d", of_port) + self.dataplane.send(of_port, pkt) + verify_packet_in(self, pkt, of_port, ofp.OFPR_ACTION) + verify_packets(self, pkt, []) + +class PacketInMiss(base_tests.SimpleDataPlane): + """ + Test packet in function for a table-miss flow + + Send a packet to each dataplane port and verify that a packet + in message is received from the controller for each + """ + def runTest(self): + delete_all_flows(self.controller) + + parsed_pkt = simple_tcp_packet() + pkt = str(parsed_pkt) + + request = ofp.message.flow_add( + table_id=test_param_get("table", 0), + cookie=42, + instructions=[ + ofp.instruction.apply_actions( + actions=[ + ofp.action.output( + port=ofp.OFPP_CONTROLLER, + max_len=ofp.OFPCML_NO_BUFFER)])], + buffer_id=ofp.OFP_NO_BUFFER, + priority=0) + + logging.info("Inserting table-miss flow sending all packets to controller") + self.controller.message_send(request) + do_barrier(self.controller) + + for of_port in config["port_map"].keys(): + logging.info("PacketInMiss test, port %d", of_port) + self.dataplane.send(of_port, pkt) + verify_packet_in(self, pkt, of_port, ofp.OFPR_NO_MATCH) + verify_packets(self, pkt, []) + +class PacketOut(base_tests.SimpleDataPlane): + """ + Test packet out function + + Send packet out message to controller for each dataplane port and + verify the packet appears on the appropriate dataplane port + """ + def runTest(self): + pkt = str(simple_tcp_packet()) + + for of_port in config["port_map"].keys(): + msg = ofp.message.packet_out( + in_port=ofp.OFPP_CONTROLLER, + actions=[ofp.action.output(port=of_port)], + buffer_id=ofp.OFP_NO_BUFFER, + data=pkt) + + logging.info("PacketOut test, port %d", of_port) + self.controller.message_send(msg) + verify_packets(self, pkt, [of_port]) + +class FlowRemoveAll(base_tests.SimpleProtocol): + """ + Remove all flows; required for almost all tests + + Add a bunch of flows, remove them, and then make sure there are no flows left + This is an intentionally naive test to see if the baseline functionality works + and should be a precondition to any more complicated deletion test (e.g., + delete_strict vs. delete) + """ + def runTest(self): + for i in range(1,5): + logging.debug("Adding flow %d", i) + request = ofp.message.flow_add( + buffer_id=ofp.OFP_NO_BUFFER, + priority=i*1000) + self.controller.message_send(request) + do_barrier(self.controller) + + delete_all_flows(self.controller) + + logging.info("Sending flow stats request") + stats = get_flow_stats(self, ofp.match()) + self.assertEqual(len(stats), 0, "Expected empty flow stats reply") + + +## Multipart messages + +class DescStats(base_tests.SimpleProtocol): + """ + Switch description multipart transaction + + Only verifies we get a single reply. + """ + def runTest(self): + request = ofp.message.desc_stats_request() + logging.info("Sending desc stats request") + response, _ = self.controller.transact(request) + self.assertTrue(response != None, "No response to desc stats request") + logging.info(response.show()) + self.assertEquals(response.flags, 0, "Unexpected bit set in desc stats reply flags") + +class FlowStats(base_tests.SimpleProtocol): + """ + Flow stats multipart transaction + + Only verifies we get a reply. + """ + def runTest(self): + logging.info("Sending flow stats request") + stats = get_flow_stats(self, ofp.match()) + logging.info("Received %d flow stats entries", len(stats)) + for entry in stats: + logging.info(entry.show()) + +class AggregateStats(base_tests.SimpleProtocol): + """ + Aggregate flow stats multipart transaction + + Only verifies we get a single reply. + """ + def runTest(self): + request = ofp.message.aggregate_stats_request( + table_id=ofp.OFPTT_ALL, + out_port=ofp.OFPP_ANY, + out_group=ofp.OFPG_ANY, + cookie=0, + cookie_mask=0) + logging.info("Sending aggregate flow stats request") + response, _ = self.controller.transact(request) + self.assertTrue(response != None, "No response to aggregate stats request") + logging.info(response.show()) + self.assertEquals(response.flags, 0, "Unexpected bit set in aggregate stats reply flags") + +class TableStats(base_tests.SimpleProtocol): + """ + Table stats multipart transaction + + Only verifies we get a reply. + """ + def runTest(self): + logging.info("Sending table stats request") + stats = get_stats(self, ofp.message.table_stats_request()) + logging.info("Received %d table stats entries", len(stats)) + for entry in stats: + logging.info(entry.show()) + +class PortStats(base_tests.SimpleProtocol): + """ + Port stats multipart transaction + + Only verifies we get a reply. + """ + def runTest(self): + request = ofp.message.port_stats_request(port_no=ofp.OFPP_ANY) + logging.info("Sending port stats request") + stats = get_stats(self, request) + logging.info("Received %d port stats entries", len(stats)) + for entry in stats: + logging.info(entry.show()) + +class QueueStats(base_tests.SimpleProtocol): + """ + Queue stats multipart transaction + + Only verifies we get a reply. + """ + def runTest(self): + request = ofp.message.queue_stats_request(port_no=ofp.OFPP_ANY, + queue_id=ofp.OFPQ_ALL) + logging.info("Sending queue stats request") + stats = get_stats(self, request) + logging.info("Received %d queue stats entries", len(stats)) + for entry in stats: + logging.info(entry.show()) + +class GroupStats(base_tests.SimpleProtocol): + """ + Group stats multipart transaction + + Only verifies we get a reply. + """ + def runTest(self): + request = ofp.message.group_stats_request(group_id=ofp.OFPG_ALL) + logging.info("Sending group stats request") + stats = get_stats(self, request) + logging.info("Received %d group stats entries", len(stats)) + for entry in stats: + logging.info(entry.show()) + +class GroupDescStats(base_tests.SimpleProtocol): + """ + Group description multipart transaction + + Only verifies we get a reply. + """ + def runTest(self): + request = ofp.message.group_desc_stats_request() + logging.info("Sending group desc stats request") + stats = get_stats(self, request) + logging.info("Received %d group desc stats entries", len(stats)) + for entry in stats: + logging.info(entry.show()) + +class GroupFeaturesStats(base_tests.SimpleProtocol): + """ + Group features multipart transaction + + Only verifies we get a single reply. + """ + def runTest(self): + request = ofp.message.group_features_stats_request() + logging.info("Sending group features stats request") + response, _ = self.controller.transact(request) + self.assertTrue(response != None, "No response to group features stats request") + logging.info(response.show()) + self.assertEquals(response.flags, 0, "Unexpected bit set in group features stats reply flags") + +class MeterStats(base_tests.SimpleProtocol): + """ + Meter stats multipart transaction + + Only verifies we get a reply. + """ + def runTest(self): + request = ofp.message.meter_stats_request(meter_id=ofp.OFPM_ALL) + logging.info("Sending meter stats request") + stats = get_stats(self, request) + logging.info("Received %d meter stats entries", len(stats)) + for entry in stats: + logging.info(entry.show()) + +class MeterConfigStats(base_tests.SimpleProtocol): + """ + Meter config multipart transaction + + Only verifies we get a reply. + """ + def runTest(self): + request = ofp.message.meter_config_stats_request(meter_id=ofp.OFPM_ALL) + logging.info("Sending meter config stats request") + stats = get_stats(self, request) + logging.info("Received %d meter config stats entries", len(stats)) + for entry in stats: + logging.info(entry.show()) + +class MeterFeaturesStats(base_tests.SimpleProtocol): + """ + Meter features multipart transaction + + Only verifies we get a single reply. + """ + def runTest(self): + request = ofp.message.meter_features_stats_request() + logging.info("Sending meter features stats request") + response, _ = self.controller.transact(request) + self.assertTrue(response != None, "No response to meter features stats request") + logging.info(response.show()) + self.assertEquals(response.flags, 0, "Unexpected bit set in meter features stats reply flags") + +@disabled # pyloxi does not yet support table features +class TableFeaturesStats(base_tests.SimpleProtocol): + """ + Table features multipart transaction + + Only verifies we get a reply. + """ + def runTest(self): + logging.info("Sending table features stats request") + stats = get_stats(self, ofp.message.table_features_stats_request()) + logging.info("Received %d table features stats entries", len(stats)) + for entry in stats: + logging.info(entry.show()) + +class PortDescStats(base_tests.SimpleProtocol): + """ + Port description multipart transaction + + Only verifies we get a reply. + """ + def runTest(self): + logging.info("Sending port desc stats request") + stats = get_stats(self, ofp.message.port_desc_stats_request()) + logging.info("Received %d port desc stats entries", len(stats)) + for entry in stats: + logging.info(entry.show()) + +class PortConfigMod(base_tests.SimpleProtocol): + """ + Modify a bit in port config and verify changed + + Get the switch configuration, modify the port configuration + and write it back; get the config again and verify changed. + Then set it back to the way it was. + """ + + def runTest(self): + logging.info("Running " + str(self)) + for of_port, _ in config["port_map"].items(): # Grab first port + break + + (_, config1, _) = \ + port_config_get(self.controller, of_port) + self.assertTrue(config is not None, "Did not get port config") + + logging.debug("OFPPC_NO_PACKET_IN bit port " + str(of_port) + " is now " + + str(config1 & ofp.OFPPC_NO_PACKET_IN)) + + rv = port_config_set(self.controller, of_port, + config1 ^ ofp.OFPPC_NO_PACKET_IN, + ofp.OFPPC_NO_PACKET_IN) + self.assertTrue(rv != -1, "Error sending port mod") + + # Verify change took place with same feature request + (_, config2, _) = port_config_get(self.controller, of_port) + self.assertTrue(config2 is not None, "Did not get port config2") + logging.debug("OFPPC_NO_PACKET_IN bit port " + str(of_port) + " is now " + + str(config2 & ofp.OFPPC_NO_PACKET_IN)) + self.assertTrue(config2 & ofp.OFPPC_NO_PACKET_IN != + config1 & ofp.OFPPC_NO_PACKET_IN, + "Bit change did not take") + # Set it back + rv = port_config_set(self.controller, of_port, config1, + ofp.OFPPC_NO_PACKET_IN) + self.assertTrue(rv != -1, "Error sending port mod") + +class AsyncConfigGet(base_tests.SimpleProtocol): + """ + Verify initial async config + + Other tests rely on connections starting with these values. + """ + + def runTest(self): + logging.info("Sending get async config request") + response, _ = self.controller.transact(ofp.message.async_get_request()) + self.assertTrue(response != None, "No response to get async config request") + logging.info(response.show()) + self.assertEquals(response.packet_in_mask_equal_master & 0x07, 0x07) + self.assertEquals(response.port_status_mask_equal_master & 0x07, 0x07) + self.assertEquals(response.flow_removed_mask_equal_master & 0x0f, 0x0f) diff --git a/tests-ofdpa-2.0/ofdpa_utils.py b/tests-ofdpa-2.0/ofdpa_utils.py new file mode 100644 index 000000000..e72cd26da --- /dev/null +++ b/tests-ofdpa-2.0/ofdpa_utils.py @@ -0,0 +1,70 @@ +""" +A set of inter-test utility functions for dealing with OF-DPA + + +""" + +import logging +import ofp + +from oftest import config +from oftest.testutils import * + + + +class table(object): + """ Metadata on each OFDPA table """ + def __init__(self, table_id, table_name): + self.table_id = table_id + self.table_name = table_name + # TODO consider adding type checking verification here + +INGRESS_TABLE = table(0, "Ingress") +VLAN_TABLE = table(10, "VLAN") +MACTERM_TABLE = table(20, "MacTerm") +UNICAST_ROUTING_TABLE = table(30, "Unicast Routing") +MULTICAST_ROUTING_TABLE = table(40, "Multicast Routing") +BRIDGING_TABLE = table(50, "Bridging Table") +ACL_TABLE = table(60, "ACL Policy Table") +#.... FIXME add all tables + + +def installDefaultVlan(controller, vlan=1, priority=0, port=ofp.OFPP_ALL): + """ Insert a rule that maps all untagged traffic to vlan $vlan + + In OFDPA, table 10 (the vlan table) requires that all traffic be + mapped to an internal vlan else the packets be dropped. This sets + up a default vlan mapping for all traffic. + + The 'controller' variable is self.controller from a test + """ + # OFDPA seems to be dumb and wants each port set individually + if port == ofp.OFPP_ALL: + ports = sorted(config["port_map"].keys()) + else: + ports = [port] + + for port in ports: + match = ofp.match([ + ofp.oxm.in_port(port), + ofp.oxm.vlan_vid(0) + ]) + + request = ofp.message.flow_add( + table_id = VLAN_TABLE.table_id, + cookie = 42, + match = match, + instructions = [ + ofp.instruction.apply_actions( + actions=[ + ofp.action.push_vlan(ethertype=0x8100), + ofp.action.set_field(ofp.oxm.vlan_vid(ofp.OFPVID_PRESENT | vlan)) + ]), + ofp.instruction.goto_table(MACTERM_TABLE.table_id) + ], + buffer_id = ofp.OFP_NO_BUFFER, + priority = priority) + + logging.info("Inserting default vlan sending all untagged traffic to vlan %d" % vlan) + controller.message_send(request) + do_barrier(controller) From 7f440ff9bedfb54c14f39c2234ba9a17d27e4037 Mon Sep 17 00:00:00 2001 From: Rob Sherwood Date: Fri, 24 Jul 2015 17:09:25 -0700 Subject: [PATCH 02/12] Fixed OF-DPA VLAN issues With OF-DPA, you need two flow_mods per port to do 'VLAN access' mode. basic.PacketInExact and basic.PacketInWildcard now working basic.AggregateStats ... ok basic.AsyncConfigGet ... ERROR basic.DefaultDrop ... ok basic.DescStats ... ok basic.Echo ... ok basic.EchoWithData ... ok basic.FeaturesRequest ... ok basic.FlowRemoveAll ... ok basic.FlowStats ... ok basic.GroupDescStats ... ok basic.GroupFeaturesStats ... ERROR basic.GroupStats ... ok basic.MeterConfigStats ... FAIL basic.MeterFeaturesStats ... ERROR basic.MeterStats ... FAIL basic.OutputExact ... FAIL basic.OutputWildcard ... FAIL basic.PacketInExact ... ok basic.PacketInMiss ... FAIL basic.PacketInWildcard ... ok basic.PacketOut ... ok basic.PortConfigMod ... ok basic.PortDescStats ... ok basic.PortStats ... ok basic.QueueStats ... ok basic.TableStats ... ok --- tests-ofdpa-2.0/basic.py | 42 +++++++++++++++++++++++++------ tests-ofdpa-2.0/ofdpa_utils.py | 46 +++++++++++++++++++++++++++++----- 2 files changed, 75 insertions(+), 13 deletions(-) diff --git a/tests-ofdpa-2.0/basic.py b/tests-ofdpa-2.0/basic.py index fc1d95d9f..c8b00b1ff 100644 --- a/tests-ofdpa-2.0/basic.py +++ b/tests-ofdpa-2.0/basic.py @@ -172,8 +172,14 @@ def runTest(self): parsed_pkt = simple_tcp_packet() pkt = str(parsed_pkt) - match = packet_to_flow_match(self, parsed_pkt) - match.vlan_vid = 1 + + # NOTE: interally the switch adds a VLAN so the match needs to be with an explicit VLAN + parsed_vlan_pkt = simple_tcp_packet(dl_vlan_enable=True, + vlan_vid=ofdpa_utils.DEFAULT_VLAN, + vlan_pcp=0, + pktlen=104) # 4 less than we started with, because the way simple_tcp calc's length + match = packet_to_flow_match(self, parsed_vlan_pkt) + vlan_pkt = str(parsed_vlan_pkt) request = ofp.message.flow_add( table_id=ofdpa_utils.ACL_TABLE.table_id, @@ -195,10 +201,11 @@ def runTest(self): for of_port in config["port_map"].keys(): logging.info("PacketInExact test, port %d", of_port) self.dataplane.send(of_port, pkt) - verify_packet_in(self, pkt, of_port, ofp.OFPR_ACTION) + verify_packet_in(self, vlan_pkt, of_port, ofp.OFPR_ACTION) verify_packets(self, pkt, []) class PacketInWildcard(base_tests.SimpleDataPlane): + # NOTE: interally the switch adds a VLAN so the match needs to be with an explicit VLAN """ Test packet in function for a match-all flow @@ -208,10 +215,21 @@ class PacketInWildcard(base_tests.SimpleDataPlane): def runTest(self): delete_all_flows(self.controller) + # required for OF-DPA to not drop packets + ofdpa_utils.installDefaultVlan(self.controller) + pkt = str(simple_tcp_packet()) + # NOTE: interally the switch adds a VLAN so the match needs to be with an explicit VLAN + parsed_vlan_pkt = simple_tcp_packet(dl_vlan_enable=True, + vlan_vid=ofdpa_utils.DEFAULT_VLAN, + vlan_pcp=0, + pktlen=104) # 4 less than we started with, because the way simple_tcp calc's length + vlan_pkt = str(parsed_vlan_pkt) + + request = ofp.message.flow_add( - table_id=test_param_get("table", 0), + table_id=ofdpa_utils.ACL_TABLE.table_id, cookie=42, instructions=[ ofp.instruction.apply_actions( @@ -229,7 +247,7 @@ def runTest(self): for of_port in config["port_map"].keys(): logging.info("PacketInWildcard test, port %d", of_port) self.dataplane.send(of_port, pkt) - verify_packet_in(self, pkt, of_port, ofp.OFPR_ACTION) + verify_packet_in(self, vlan_pkt, of_port, ofp.OFPR_ACTION) verify_packets(self, pkt, []) class PacketInMiss(base_tests.SimpleDataPlane): @@ -242,11 +260,21 @@ class PacketInMiss(base_tests.SimpleDataPlane): def runTest(self): delete_all_flows(self.controller) + # required for OF-DPA to not drop packets + ofdpa_utils.installDefaultVlan(self.controller) + parsed_pkt = simple_tcp_packet() pkt = str(parsed_pkt) + # NOTE: interally the switch adds a VLAN so the match needs to be with an explicit VLAN + parsed_vlan_pkt = simple_tcp_packet(dl_vlan_enable=True, + vlan_vid=ofdpa_utils.DEFAULT_VLAN, + vlan_pcp=0, + pktlen=104) # 4 less than we started with, because the way simple_tcp calc's length + vlan_pkt = str(parsed_vlan_pkt) + request = ofp.message.flow_add( - table_id=test_param_get("table", 0), + table_id=ofdpa_utils.ACL_TABLE.table_id, cookie=42, instructions=[ ofp.instruction.apply_actions( @@ -264,7 +292,7 @@ def runTest(self): for of_port in config["port_map"].keys(): logging.info("PacketInMiss test, port %d", of_port) self.dataplane.send(of_port, pkt) - verify_packet_in(self, pkt, of_port, ofp.OFPR_NO_MATCH) + verify_packet_in(self, vlan_pkt, of_port, ofp.OFPR_NO_MATCH) verify_packets(self, pkt, []) class PacketOut(base_tests.SimpleDataPlane): diff --git a/tests-ofdpa-2.0/ofdpa_utils.py b/tests-ofdpa-2.0/ofdpa_utils.py index e72cd26da..fb2c3bd6c 100644 --- a/tests-ofdpa-2.0/ofdpa_utils.py +++ b/tests-ofdpa-2.0/ofdpa_utils.py @@ -28,14 +28,22 @@ def __init__(self, table_id, table_name): ACL_TABLE = table(60, "ACL Policy Table") #.... FIXME add all tables +DEFAULT_VLAN = 1 -def installDefaultVlan(controller, vlan=1, priority=0, port=ofp.OFPP_ALL): + +def installDefaultVlan(controller, vlan=DEFAULT_VLAN, priority=0, port=ofp.OFPP_ALL): """ Insert a rule that maps all untagged traffic to vlan $vlan In OFDPA, table 10 (the vlan table) requires that all traffic be mapped to an internal vlan else the packets be dropped. This sets up a default vlan mapping for all traffic. + With OF-DPA, before you can insert a 'untagged to X' rule on a + port, you must first insert a 'X --> X' rule for the same port. + + Further, the 'X --> X' rule must set ofp.OFPVID_PRESENT even + though 'X' is non-zero. + The 'controller' variable is self.controller from a test """ # OFDPA seems to be dumb and wants each port set individually @@ -45,19 +53,45 @@ def installDefaultVlan(controller, vlan=1, priority=0, port=ofp.OFPP_ALL): ports = [port] for port in ports: - match = ofp.match([ + tagged_match = ofp.match([ + ofp.oxm.in_port(port), + ofp.oxm.vlan_vid(vlan | ofp.OFPVID_PRESENT) + ]) + + untagged_match = ofp.match([ ofp.oxm.in_port(port), ofp.oxm.vlan_vid(0) ]) + # first install the rule that allows this vlan tagged + request = ofp.message.flow_add( + table_id = VLAN_TABLE.table_id, + cookie = 0xdead, + match = tagged_match, + instructions = [ + ofp.instruction.apply_actions( + actions=[ +# ofp.action.push_vlan(ethertype=0x8100), # DO NOT PUT THIS FOR OF-DPA + ofp.action.set_field(ofp.oxm.vlan_vid(vlan)) + ]), + ofp.instruction.goto_table(MACTERM_TABLE.table_id) + ], + buffer_id = ofp.OFP_NO_BUFFER, + priority = priority) + + logging.info("Inserting access vlan rule allowing vlan %d on port %d" % (vlan, port)) + controller.message_send(request) + do_barrier(controller) + + request = ofp.message.flow_add( table_id = VLAN_TABLE.table_id, - cookie = 42, - match = match, + cookie = 0xbeef, + match = untagged_match, instructions = [ ofp.instruction.apply_actions( actions=[ - ofp.action.push_vlan(ethertype=0x8100), +# ofp.action.push_vlan(ethertype=0x8100), ofp.action.set_field(ofp.oxm.vlan_vid(ofp.OFPVID_PRESENT | vlan)) ]), ofp.instruction.goto_table(MACTERM_TABLE.table_id) @@ -65,6 +99,6 @@ def installDefaultVlan(controller, vlan=1, priority=0, port=ofp.OFPP_ALL): buffer_id = ofp.OFP_NO_BUFFER, priority = priority) - logging.info("Inserting default vlan sending all untagged traffic to vlan %d" % vlan) + logging.info("Inserting default vlan sending all untagged traffic to vlan %d on port %d" % (vlan, port)) controller.message_send(request) do_barrier(controller) From 5a97a4c425b22e9c7da68b5366df95fa91ca8d18 Mon Sep 17 00:00:00 2001 From: Rob Sherwood Date: Sun, 26 Jul 2015 13:27:07 -0700 Subject: [PATCH 03/12] Added VIM swap files to the .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 48da8416f..46d206c5c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.pyc profiles/*.pyc *~ +.*.swp From 267025230b15958dbc0870fc6c143f8e9dbc2c3e Mon Sep 17 00:00:00 2001 From: Rob Sherwood Date: Sun, 26 Jul 2015 19:52:43 -0700 Subject: [PATCH 04/12] OF-DPA: mostly workin IPv6 forwarding test --- tests-ofdpa-2.0/ipv6_fwd.py | 171 +++++++++++++++++++++++++++++++++ tests-ofdpa-2.0/ofdpa_utils.py | 96 +++++++++++++----- 2 files changed, 242 insertions(+), 25 deletions(-) create mode 100644 tests-ofdpa-2.0/ipv6_fwd.py diff --git a/tests-ofdpa-2.0/ipv6_fwd.py b/tests-ofdpa-2.0/ipv6_fwd.py new file mode 100644 index 000000000..a5f4c4a2b --- /dev/null +++ b/tests-ofdpa-2.0/ipv6_fwd.py @@ -0,0 +1,171 @@ +# Copyright (c) 2015 Big Switch Networks, Inc. +""" +Testing IPv6 forwarding + +Test cases in other modules depend on this functionality. +""" + +import logging +import pdb + +from oftest import config +import oftest.base_tests as base_tests +import ofp +import ofdpa_utils +from oftest.parse import parse_ipv6 + +from oftest.testutils import * +import oftest.oft12.testutils as testutils12 + +def macAddrToHexList(mac): + """ Takes '00:11:22:33:44:ff' and returns [ 0x00, 0x11, 0x22, 0x33, 0x44, 0xff] """ + return map(int,mac.split(':'),[16]*6) + +@group('ipv6_fwd') +class IPv6_Untagged_Unicast(base_tests.SimpleDataPlane): + """ + Test routing function for an IPv6 IP packet + + Need to insert rules into: + 1) A group action in the group table + 2) Vlan table to map packet to a vlan + 3) Mac Term table to send router's mac packets to the routing table + 4) the actual routing rule in the unicast routing table + + Then test from all ports to all ports + + """ + def runTest(self): + router_mac = '00:11:22:33:44:55' + dst_ip = 'fe80::2420:52ff:fe8f:5190' + dst_mac = '00:11:11:11:11:11' + dst_vlan = 2 # used for internal switch purposes + ports = sorted(config["port_map"].keys()) + + delete_all_flows(self.controller) + delete_all_groups(self.controller) + + #pdb.set_trace() + l3_group_id = ofdpa_utils.makeGroupID("L3 Unicast",1) + l2_group_id = ofdpa_utils.makeGroupID("L2 Interface",(dst_vlan<<16) + 1) # bits 27:16 of the local_id are the vlan (!?) + + # OF-DPA requires vlans to function + ofdpa_utils.installDefaultVlan(self.controller) + ofdpa_utils.enableVlanOnPort(self.controller, dst_vlan) + + + # Create the L2 interface group entry + # Note that the port here is temp; will be reset in the testing loop + group_msg = ofp.message.group_add( + group_type=ofp.OFPGT_ALL, + group_id=l2_group_id, + buckets=[ + ofp.bucket( + actions=[ + ofp.action.pop_vlan(), + ofp.action.output(ports[0]) # set port now, but inner loop will change + ]) + ]) + + logging.info("Creating IPv6 L2 interface group") + self.controller.message_send(group_msg) + do_barrier(self.controller) + verify_no_errors(self.controller) + + + # Create the L3 unicast group entry + group_msg = ofp.message.group_add( + group_type=ofp.OFPGT_ALL, + group_id=l3_group_id, + buckets=[ + ofp.bucket( + actions=[ + ofp.action.group(l2_group_id), # chain to L2 group + ofp.action.set_field( ofp.oxm.eth_dst( macAddrToHexList(dst_mac))), + ofp.action.set_field( ofp.oxm.eth_src( macAddrToHexList(router_mac))), + ofp.action.set_field( ofp.oxm.vlan_vid( dst_vlan)), + ]) + ]) + + logging.info("Creating IPv6 Unicast group") + self.controller.message_send(group_msg) + do_barrier(self.controller) + verify_no_errors(self.controller) + + # Insert a rule in the MAC termination table to send to Unicast router table + # Must match on dst mac and ethertype; other fields are optional + macterm_match = ofp.match([ + ofp.oxm.eth_dst(macAddrToHexList(router_mac)), + ofp.oxm.eth_type(0x86dd) # ethertype = IPv6 + ]) + + request = ofp.message.flow_add( + table_id = ofdpa_utils.MACTERM_TABLE.table_id, + cookie=55, + match=macterm_match, + instructions=[ + ofp.instruction.goto_table(ofdpa_utils.UNICAST_ROUTING_TABLE.table_id) + ], + buffer_id=ofp.OFP_NO_BUFFER, + priority=1000) + + logging.info("Inserting IPv6 MacTermination rule") + self.controller.message_send(request) + do_barrier(self.controller) + verify_no_errors(self.controller) + + # Insert a rule into the Unicast Routing table to forward this packet + unicast_match = ofp.match([ + ofp.oxm.eth_type(0x86dd), # ethertype = IPv6 + ofp.oxm.ipv6_dst(parse_ipv6(dst_ip)), + ]) + + request = ofp.message.flow_add( + table_id = ofdpa_utils.UNICAST_ROUTING_TABLE.table_id, + cookie=66, + match=unicast_match, + instructions=[ + ofp.instruction.goto_table(ofdpa_utils.ACL_TABLE.table_id), + ofp.instruction.write_actions([ofp.action.group(l3_group_id)]) + ], + buffer_id=ofp.OFP_NO_BUFFER, + priority=1000) + + logging.info("Inserting IPv6 MacTermination rule") + self.controller.message_send(request) + do_barrier(self.controller) + verify_no_errors(self.controller) + + # Generate the test and expected packets + parsed_pkt = testutils12.simple_ipv6_packet() + pkt = str(parsed_pkt) + + parsed_expected_pkt = testutils12.simple_ipv6_packet( + dl_dst = router_mac, +# dl_vlan_enable=True, +# vlan_vid=ofdpa_utils.DEFAULT_VLAN, +# vlan_pcp=0, +# pktlen=104 # 4 less than we started with, because the way simple_tcp calc's length + ) + expected_pkt = str(parsed_expected_pkt) + + for out_port in ports: + msg = ofp.message.group_modify( + group_type=ofp.OFPGT_ALL, + group_id=l2_group_id, + buckets=[ + ofp.action.pop_vlan(), + ofp.bucket(actions=[ofp.action.output(out_port)]) + ]) + + self.controller.message_send(msg) + logging.info("Changing output port for group %d to %d" % (l2_group_id, out_port)) + do_barrier(self.controller) + verify_no_errors(self.controller) + + for in_port in ports: + if in_port == out_port: + continue + logging.info("IPv6_Untagged_Unicast test, ports %d to %d", in_port, out_port) + self.dataplane.send(in_port, pkt) + verify_packets(self, expected_pkt, [out_port]) diff --git a/tests-ofdpa-2.0/ofdpa_utils.py b/tests-ofdpa-2.0/ofdpa_utils.py index fb2c3bd6c..406515453 100644 --- a/tests-ofdpa-2.0/ofdpa_utils.py +++ b/tests-ofdpa-2.0/ofdpa_utils.py @@ -30,40 +30,17 @@ def __init__(self, table_id, table_name): DEFAULT_VLAN = 1 - -def installDefaultVlan(controller, vlan=DEFAULT_VLAN, priority=0, port=ofp.OFPP_ALL): - """ Insert a rule that maps all untagged traffic to vlan $vlan - - In OFDPA, table 10 (the vlan table) requires that all traffic be - mapped to an internal vlan else the packets be dropped. This sets - up a default vlan mapping for all traffic. - - With OF-DPA, before you can insert a 'untagged to X' rule on a - port, you must first insert a 'X --> X' rule for the same port. - - Further, the 'X --> X' rule must set ofp.OFPVID_PRESENT even - though 'X' is non-zero. - - The 'controller' variable is self.controller from a test - """ - # OFDPA seems to be dumb and wants each port set individually +def enableVlanOnPort(controller, vlan, port=ofp.OFPP_ALL, priority=0): if port == ofp.OFPP_ALL: ports = sorted(config["port_map"].keys()) else: ports = [port] - for port in ports: tagged_match = ofp.match([ ofp.oxm.in_port(port), ofp.oxm.vlan_vid(vlan | ofp.OFPVID_PRESENT) ]) - untagged_match = ofp.match([ - ofp.oxm.in_port(port), - ofp.oxm.vlan_vid(0) - ]) - - # first install the rule that allows this vlan tagged request = ofp.message.flow_add( table_id = VLAN_TABLE.table_id, cookie = 0xdead, @@ -79,10 +56,42 @@ def installDefaultVlan(controller, vlan=DEFAULT_VLAN, priority=0, port=ofp.OFPP_ buffer_id = ofp.OFP_NO_BUFFER, priority = priority) - logging.info("Inserting access vlan rule allowing vlan %d on port %d" % (vlan, port)) + logging.info("Inserting vlan rule allowing tagged vlan %d on port %d" % (vlan, port)) controller.message_send(request) do_barrier(controller) + verify_no_errors(controller) + +def installDefaultVlan(controller, vlan=DEFAULT_VLAN, port=ofp.OFPP_ALL, priority=0): + """ Insert a rule that maps all untagged traffic to vlan $vlan + + In OFDPA, table 10 (the vlan table) requires that all traffic be + mapped to an internal vlan else the packets be dropped. This function + sets up a default vlan mapping all untagged traffic to an internal VLAN. + + With OF-DPA, before you can insert a 'untagged to X' rule on a + port, you must first insert a 'X --> X' rule for the same port. + + Further, the 'X --> X' rule must set ofp.OFPVID_PRESENT even + though 'X' is non-zero. + + The 'controller' variable is self.controller from a test + """ + # OFDPA seems to be dumb and wants each port set individually + # Can't set all ports by using OFPP_ALL + if port == ofp.OFPP_ALL: + ports = sorted(config["port_map"].keys()) + else: + ports = [port] + + for port in ports: + # enable this vlan on this port before we can map untagged packets to the vlan + enableVlanOnPort(controller, vlan, port) + + untagged_match = ofp.match([ + ofp.oxm.in_port(port), + ofp.oxm.vlan_vid(0) + ]) request = ofp.message.flow_add( table_id = VLAN_TABLE.table_id, @@ -102,3 +111,40 @@ def installDefaultVlan(controller, vlan=DEFAULT_VLAN, priority=0, port=ofp.OFPP_ logging.info("Inserting default vlan sending all untagged traffic to vlan %d on port %d" % (vlan, port)) controller.message_send(request) do_barrier(controller) + verify_no_errors(controller) + + +_group_types = { + "L2 Interface": 0, + "L2 Rewrite" : 1, + "L3 Unicast" : 2, + "L2 Multicast" : 3, + "L2 Flood" : 4, + "L3 Interface" : 5, + "L3 Multicast": 6, + "L3 ECMP": 7, + "L2 Data Center Overlay": 8, + "MPLS Label" : 9, + "MPLS Forwarding" :10, + "L2 Unfiltered Interface": 11, + "L2 Loopback": 12, +} + + +def makeGroupID(groupType, local_id): + """ Group IDs in OF-DPA have rich meaning + + @param groupType is a key in _group_types + @param local_id is an integer 0<= local_id < 2**27, + but it may have more semantic meaning depending on the + groupType + + + Read Section 4.3 of the OF-DPA manual on groups for + details + """ + if groupType not in _group_types: + raise KeyError("%s not a valid OF-DPA group type" % groupType) + if local_id < 0 or local_id >=134217728: + raise ValueError("local_id %d must be 0<= local_id < 2**27" % local_id) + return (_group_types[groupType] << 28) + local_id From 150addc1596ebeabfee14d614c68ee66d601efbe Mon Sep 17 00:00:00 2001 From: Rob Sherwood Date: Sun, 26 Jul 2015 23:09:14 -0700 Subject: [PATCH 05/12] Current iteration on ipv6_fwd test Insert new groups at each iteration --- tests-ofdpa-2.0/ipv6_fwd.py | 132 +++++++++++++++--------------------- 1 file changed, 55 insertions(+), 77 deletions(-) diff --git a/tests-ofdpa-2.0/ipv6_fwd.py b/tests-ofdpa-2.0/ipv6_fwd.py index a5f4c4a2b..3870f1941 100644 --- a/tests-ofdpa-2.0/ipv6_fwd.py +++ b/tests-ofdpa-2.0/ipv6_fwd.py @@ -21,6 +21,8 @@ def macAddrToHexList(mac): """ Takes '00:11:22:33:44:ff' and returns [ 0x00, 0x11, 0x22, 0x33, 0x44, 0xff] """ return map(int,mac.split(':'),[16]*6) + + @group('ipv6_fwd') class IPv6_Untagged_Unicast(base_tests.SimpleDataPlane): """ @@ -35,6 +37,12 @@ class IPv6_Untagged_Unicast(base_tests.SimpleDataPlane): Then test from all ports to all ports """ + def send_log_check(self, log_msg, of_msg): + logging.info(log_msg) + self.controller.message_send(of_msg) + do_barrier(self.controller) + verify_no_errors(self.controller) + def runTest(self): router_mac = '00:11:22:33:44:55' dst_ip = 'fe80::2420:52ff:fe8f:5190' @@ -45,52 +53,13 @@ def runTest(self): delete_all_flows(self.controller) delete_all_groups(self.controller) - #pdb.set_trace() - l3_group_id = ofdpa_utils.makeGroupID("L3 Unicast",1) - l2_group_id = ofdpa_utils.makeGroupID("L2 Interface",(dst_vlan<<16) + 1) # bits 27:16 of the local_id are the vlan (!?) - # OF-DPA requires vlans to function ofdpa_utils.installDefaultVlan(self.controller) ofdpa_utils.enableVlanOnPort(self.controller, dst_vlan) - # Create the L2 interface group entry + # Create an L2 and L3 nterface group entry for each port # Note that the port here is temp; will be reset in the testing loop - group_msg = ofp.message.group_add( - group_type=ofp.OFPGT_ALL, - group_id=l2_group_id, - buckets=[ - ofp.bucket( - actions=[ - ofp.action.pop_vlan(), - ofp.action.output(ports[0]) # set port now, but inner loop will change - ]) - ]) - - logging.info("Creating IPv6 L2 interface group") - self.controller.message_send(group_msg) - do_barrier(self.controller) - verify_no_errors(self.controller) - - - # Create the L3 unicast group entry - group_msg = ofp.message.group_add( - group_type=ofp.OFPGT_ALL, - group_id=l3_group_id, - buckets=[ - ofp.bucket( - actions=[ - ofp.action.group(l2_group_id), # chain to L2 group - ofp.action.set_field( ofp.oxm.eth_dst( macAddrToHexList(dst_mac))), - ofp.action.set_field( ofp.oxm.eth_src( macAddrToHexList(router_mac))), - ofp.action.set_field( ofp.oxm.vlan_vid( dst_vlan)), - ]) - ]) - - logging.info("Creating IPv6 Unicast group") - self.controller.message_send(group_msg) - do_barrier(self.controller) - verify_no_errors(self.controller) # Insert a rule in the MAC termination table to send to Unicast router table # Must match on dst mac and ethertype; other fields are optional @@ -99,7 +68,7 @@ def runTest(self): ofp.oxm.eth_type(0x86dd) # ethertype = IPv6 ]) - request = ofp.message.flow_add( + self.send_log_check( "Inserting IPv6 MacTermination rule", ofp.message.flow_add( table_id = ofdpa_utils.MACTERM_TABLE.table_id, cookie=55, match=macterm_match, @@ -107,34 +76,13 @@ def runTest(self): ofp.instruction.goto_table(ofdpa_utils.UNICAST_ROUTING_TABLE.table_id) ], buffer_id=ofp.OFP_NO_BUFFER, - priority=1000) - - logging.info("Inserting IPv6 MacTermination rule") - self.controller.message_send(request) - do_barrier(self.controller) - verify_no_errors(self.controller) + priority=1000)) - # Insert a rule into the Unicast Routing table to forward this packet + # Store the match for the unicast match below unicast_match = ofp.match([ - ofp.oxm.eth_type(0x86dd), # ethertype = IPv6 - ofp.oxm.ipv6_dst(parse_ipv6(dst_ip)), - ]) - - request = ofp.message.flow_add( - table_id = ofdpa_utils.UNICAST_ROUTING_TABLE.table_id, - cookie=66, - match=unicast_match, - instructions=[ - ofp.instruction.goto_table(ofdpa_utils.ACL_TABLE.table_id), - ofp.instruction.write_actions([ofp.action.group(l3_group_id)]) - ], - buffer_id=ofp.OFP_NO_BUFFER, - priority=1000) - - logging.info("Inserting IPv6 MacTermination rule") - self.controller.message_send(request) - do_barrier(self.controller) - verify_no_errors(self.controller) + ofp.oxm.eth_type(0x86dd), # ethertype = IPv6 + ofp.oxm.ipv6_dst(parse_ipv6(dst_ip)), + ]) # Generate the test and expected packets parsed_pkt = testutils12.simple_ipv6_packet() @@ -149,19 +97,48 @@ def runTest(self): ) expected_pkt = str(parsed_expected_pkt) + + first_time = True for out_port in ports: - msg = ofp.message.group_modify( - group_type=ofp.OFPGT_ALL, - group_id=l2_group_id, - buckets=[ + l2_group_id = ofdpa_utils.makeGroupID("L2 Interface",(dst_vlan<<16) + out_port) # bits 27:16 of the local_id are the vlan (!?) + l3_group_id = ofdpa_utils.makeGroupID("L3 Unicast",out_port) + self.send_log_check("Creating IPv6 L2 interface group for port %d" % out_port, ofp.message.group_add( + group_type=ofp.OFPGT_ALL, + group_id=l2_group_id, + buckets=[ + ofp.bucket( + actions=[ ofp.action.pop_vlan(), - ofp.bucket(actions=[ofp.action.output(out_port)]) + ofp.action.output(out_port) ]) - - self.controller.message_send(msg) - logging.info("Changing output port for group %d to %d" % (l2_group_id, out_port)) - do_barrier(self.controller) - verify_no_errors(self.controller) + ])) + + # Create the L3 unicast group entry + self.send_log_check("Creating IPv6 Unicast group for %d" % out_port, ofp.message.group_add( + group_type=ofp.OFPGT_ALL, + group_id=l3_group_id, + buckets=[ + ofp.bucket( + actions=[ + ofp.action.group(l2_group_id), # chain to last L2 group; doesn't matter; will fix in test loop + ofp.action.set_field( ofp.oxm.eth_dst( macAddrToHexList(dst_mac))), + ofp.action.set_field( ofp.oxm.eth_src( macAddrToHexList(router_mac))), + ofp.action.set_field( ofp.oxm.vlan_vid( dst_vlan)), + ]) + ])) + + self.send_log_check("Modifying IPv6 route rule to output to port %d" % out_port, ofp.message.flow_modify( + table_id = ofdpa_utils.UNICAST_ROUTING_TABLE.table_id, + cookie=66, + match=unicast_match, + instructions=[ + ofp.instruction.goto_table(ofdpa_utils.ACL_TABLE.table_id), + # set the l3_group_id to correspond to out_port's buckets + ofp.instruction.write_actions([ + ofp.action.group(ofdpa_utils.makeGroupID("L3 Unicast",out_port))]) + ], + buffer_id=ofp.OFP_NO_BUFFER, + priority=1000)) for in_port in ports: if in_port == out_port: @@ -169,3 +146,4 @@ def runTest(self): logging.info("IPv6_Untagged_Unicast test, ports %d to %d", in_port, out_port) self.dataplane.send(in_port, pkt) verify_packets(self, expected_pkt, [out_port]) + From 839f0641fe9e03c0eadc5de1648959d545a3bab9 Mon Sep 17 00:00:00 2001 From: Rob Sherwood Date: Tue, 28 Jul 2015 00:40:29 -0700 Subject: [PATCH 06/12] Switch is doing the right thing; just need to fix test --- tests-ofdpa-2.0/ipv6_fwd.py | 185 ++++++++++++++++++++---------------- 1 file changed, 102 insertions(+), 83 deletions(-) diff --git a/tests-ofdpa-2.0/ipv6_fwd.py b/tests-ofdpa-2.0/ipv6_fwd.py index 3870f1941..cd57ec77d 100644 --- a/tests-ofdpa-2.0/ipv6_fwd.py +++ b/tests-ofdpa-2.0/ipv6_fwd.py @@ -21,7 +21,16 @@ def macAddrToHexList(mac): """ Takes '00:11:22:33:44:ff' and returns [ 0x00, 0x11, 0x22, 0x33, 0x44, 0xff] """ return map(int,mac.split(':'),[16]*6) - + + +class L3Iface(object): + def __init__(self, mac,vlan,port, dst_mac, dst_ip): + self.mac = mac # this is the mac of the router interface + self.port = port # this is the port of the router + self.vlan = vlan + self.dst_mac = dst_mac + self.dst_ip = dst_ip + @group('ipv6_fwd') class IPv6_Untagged_Unicast(base_tests.SimpleDataPlane): @@ -37,34 +46,24 @@ class IPv6_Untagged_Unicast(base_tests.SimpleDataPlane): Then test from all ports to all ports """ - def send_log_check(self, log_msg, of_msg): - logging.info(log_msg) - self.controller.message_send(of_msg) - do_barrier(self.controller) - verify_no_errors(self.controller) - - def runTest(self): - router_mac = '00:11:22:33:44:55' - dst_ip = 'fe80::2420:52ff:fe8f:5190' - dst_mac = '00:11:11:11:11:11' - dst_vlan = 2 # used for internal switch purposes - ports = sorted(config["port_map"].keys()) - - delete_all_flows(self.controller) - delete_all_groups(self.controller) - - # OF-DPA requires vlans to function - ofdpa_utils.installDefaultVlan(self.controller) - ofdpa_utils.enableVlanOnPort(self.controller, dst_vlan) - - - # Create an L2 and L3 nterface group entry for each port - # Note that the port here is temp; will be reset in the testing loop + def create_interface(self, iface): + l2_group_id = ofdpa_utils.makeGroupID("L2 Interface",(iface.vlan<<16) + iface.port) # bits 27:16 of the local_id are the vlan (!?) + l3_group_id = ofdpa_utils.makeGroupID("L3 Unicast",iface.port) + self.send_log_check("Creating IPv6 L2 interface group for port %d" % iface.port, ofp.message.group_add( + group_type=ofp.OFPGT_ALL, + group_id=l2_group_id, + buckets=[ + ofp.bucket( + actions=[ + ofp.action.pop_vlan(), + ofp.action.output(iface.port) + ]) + ])) # Insert a rule in the MAC termination table to send to Unicast router table # Must match on dst mac and ethertype; other fields are optional macterm_match = ofp.match([ - ofp.oxm.eth_dst(macAddrToHexList(router_mac)), + ofp.oxm.eth_dst(macAddrToHexList(iface.mac)), ofp.oxm.eth_type(0x86dd) # ethertype = IPv6 ]) @@ -78,72 +77,92 @@ def runTest(self): buffer_id=ofp.OFP_NO_BUFFER, priority=1000)) + # Create the L3 unicast group entry + self.send_log_check("Creating IPv6 Unicast group for %d" % iface.port, ofp.message.group_add( + group_type=ofp.OFPGT_ALL, + group_id=l3_group_id, + buckets=[ + ofp.bucket( + actions=[ + ofp.action.group(l2_group_id), # chain to last L2 group; doesn't matter; will fix in test loop + ofp.action.set_field( ofp.oxm.eth_dst( macAddrToHexList(iface.dst_mac))), + ofp.action.set_field( ofp.oxm.eth_src( macAddrToHexList(iface.mac))), + ofp.action.set_field( ofp.oxm.vlan_vid( iface.vlan)), + ]) + ])) + # Store the match for the unicast match below unicast_match = ofp.match([ ofp.oxm.eth_type(0x86dd), # ethertype = IPv6 - ofp.oxm.ipv6_dst(parse_ipv6(dst_ip)), + ofp.oxm.ipv6_dst(parse_ipv6(iface.dst_ip)), ]) - # Generate the test and expected packets - parsed_pkt = testutils12.simple_ipv6_packet() - pkt = str(parsed_pkt) + # OF-DPA requires vlans to function + ofdpa_utils.installDefaultVlan(self.controller, iface.vlan, iface.port) - parsed_expected_pkt = testutils12.simple_ipv6_packet( - dl_dst = router_mac, + + self.send_log_check("Modifying IPv6 route rule to output to port %d" % iface.port, ofp.message.flow_modify( + table_id = ofdpa_utils.UNICAST_ROUTING_TABLE.table_id, + cookie=66, + match=unicast_match, + instructions=[ + ofp.instruction.goto_table(ofdpa_utils.ACL_TABLE.table_id), + # set the l3_group_id to correspond to out_port's buckets + ofp.instruction.write_actions([ + ofp.action.group(ofdpa_utils.makeGroupID("L3 Unicast",iface.port))]) + ], + buffer_id=ofp.OFP_NO_BUFFER, + priority=1000)) + + + def send_log_check(self, log_msg, of_msg): + logging.info(log_msg) + self.controller.message_send(of_msg) + do_barrier(self.controller) + verify_no_errors(self.controller) + + def runTest(self): + ports = sorted(config["port_map"].keys()) + + delete_all_flows(self.controller) + delete_all_groups(self.controller) + do_barrier(self.controller) + + ifaces = { + # mac,vlan,port, dst_mac, dst_ip + L3Iface('00:11:11:11:11:11', 1, 1, '00:11:11:11:11:ff', 'fe80::1111'): 1, + L3Iface('00:22:22:22:22:22', 2, 2, '00:22:22:22:22:ff', 'fe80::2222'): 2, + L3Iface('00:33:33:33:33:33', 3, 3, '00:33:33:33:33:ff', 'fe80::3333'): 3, + L3Iface('00:44:44:44:44:44', 4, 4, '00:44:44:44:44:ff', 'fe80::4444'): 4, + L3Iface('00:55:55:55:55:55', 5, 5, '00:55:55:55:55:ff', 'fe80::5555'): 5, + L3Iface('00:66:66:66:66:66', 6, 6, '00:66:66:66:66:ff', 'fe80::6666'): 6, + } + + for iface in ifaces: + self.create_interface(iface) + + # test sending from every inteface to every other interface + for in_iface in sorted(ifaces): + for out_iface in sorted(ifaces): + if in_iface == out_iface: + continue + # Generate the test and expected packets + parsed_pkt = testutils12.simple_ipv6_packet( + ip_dst = out_iface.dst_ip, + dl_dst = in_iface.mac) + pkt = str(parsed_pkt) + + parsed_expected_pkt = testutils12.simple_ipv6_packet( + dl_src = out_iface.mac, + dl_dst = out_iface.dst_mac, + ip_dst = out_iface.dst_ip, # dl_vlan_enable=True, # vlan_vid=ofdpa_utils.DEFAULT_VLAN, # vlan_pcp=0, # pktlen=104 # 4 less than we started with, because the way simple_tcp calc's length - ) - expected_pkt = str(parsed_expected_pkt) - - - first_time = True - for out_port in ports: - l2_group_id = ofdpa_utils.makeGroupID("L2 Interface",(dst_vlan<<16) + out_port) # bits 27:16 of the local_id are the vlan (!?) - l3_group_id = ofdpa_utils.makeGroupID("L3 Unicast",out_port) - self.send_log_check("Creating IPv6 L2 interface group for port %d" % out_port, ofp.message.group_add( - group_type=ofp.OFPGT_ALL, - group_id=l2_group_id, - buckets=[ - ofp.bucket( - actions=[ - ofp.action.pop_vlan(), - ofp.action.output(out_port) - ]) - ])) - - # Create the L3 unicast group entry - self.send_log_check("Creating IPv6 Unicast group for %d" % out_port, ofp.message.group_add( - group_type=ofp.OFPGT_ALL, - group_id=l3_group_id, - buckets=[ - ofp.bucket( - actions=[ - ofp.action.group(l2_group_id), # chain to last L2 group; doesn't matter; will fix in test loop - ofp.action.set_field( ofp.oxm.eth_dst( macAddrToHexList(dst_mac))), - ofp.action.set_field( ofp.oxm.eth_src( macAddrToHexList(router_mac))), - ofp.action.set_field( ofp.oxm.vlan_vid( dst_vlan)), - ]) - ])) - - self.send_log_check("Modifying IPv6 route rule to output to port %d" % out_port, ofp.message.flow_modify( - table_id = ofdpa_utils.UNICAST_ROUTING_TABLE.table_id, - cookie=66, - match=unicast_match, - instructions=[ - ofp.instruction.goto_table(ofdpa_utils.ACL_TABLE.table_id), - # set the l3_group_id to correspond to out_port's buckets - ofp.instruction.write_actions([ - ofp.action.group(ofdpa_utils.makeGroupID("L3 Unicast",out_port))]) - ], - buffer_id=ofp.OFP_NO_BUFFER, - priority=1000)) - - for in_port in ports: - if in_port == out_port: - continue - logging.info("IPv6_Untagged_Unicast test, ports %d to %d", in_port, out_port) - self.dataplane.send(in_port, pkt) - verify_packets(self, expected_pkt, [out_port]) + ) + expected_pkt = str(parsed_expected_pkt) + logging.info("IPv6_Untagged_Unicast test, iface %d to %d", ifaces[in_iface], ifaces[out_iface]) + self.dataplane.send(in_iface.port, pkt) + verify_packets(self, expected_pkt, [out_iface.port]) From e393eaee5b12950c867404cbbf51e8dd1150246e Mon Sep 17 00:00:00 2001 From: Rob Sherwood Date: Thu, 30 Jul 2015 08:51:44 -0700 Subject: [PATCH 07/12] Added a flag to diff packets in verify_packets() Set ofdpa.dataplane.MATCH_VERBOSE to True --- src/python/oftest/dataplane.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/python/oftest/dataplane.py b/src/python/oftest/dataplane.py index 0cdbaa886..ef08e30fe 100644 --- a/src/python/oftest/dataplane.py +++ b/src/python/oftest/dataplane.py @@ -32,6 +32,8 @@ else: import pcap +MATCH_VERBOSE=False + def match_exp_pkt(exp_pkt, pkt): """ Compare the string value of pkt with the string value of exp_pkt, @@ -43,6 +45,13 @@ def match_exp_pkt(exp_pkt, pkt): p = str(pkt) if len(e) < 60: p = p[:len(e)] + if MATCH_VERBOSE and e != p: + i = 0 + for i in range(0, min(len(e), len(p))): + if e[i] != p[i]: + break + logging.info("Ignoring packet that doesn't match on byte %d: expected is \'%s\' got \'%s\'" + % (i, hex(ord(e[i])), hex(ord(p[i])))) return e == p From 7d4f05884d3f958ed1f34e40f40d2d4274a211f2 Mon Sep 17 00:00:00 2001 From: Rob Sherwood Date: Thu, 30 Jul 2015 08:53:45 -0700 Subject: [PATCH 08/12] Option to specify TTL to IPv6 packets --- src/python/oftest/oft12/testutils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/python/oftest/oft12/testutils.py b/src/python/oftest/oft12/testutils.py index d6dbdf0a4..00b099a15 100644 --- a/src/python/oftest/oft12/testutils.py +++ b/src/python/oftest/oft12/testutils.py @@ -238,6 +238,7 @@ def simple_ipv6_packet(pktlen=100, ip_src='fe80::2420:52ff:fe8f:5189', ip_dst='fe80::2420:52ff:fe8f:5190', ip_tos=0, + ip_ttl=64, tcp_sport=0, tcp_dport=0, EH = False, @@ -265,11 +266,11 @@ def simple_ipv6_packet(pktlen=100, if (dl_vlan_enable): pkt = Ether(dst=dl_dst, src=dl_src)/ \ Dot1Q(prio=dl_vlan_pcp, id=dl_vlan_cfi, vlan=dl_vlan)/ \ - IPv6(src=ip_src, dst=ip_dst) + IPv6(src=ip_src, dst=ip_dst, hlim=ip_ttl) else: pkt = Ether(dst=dl_dst, src=dl_src)/ \ - IPv6(src=ip_src, dst=ip_dst) + IPv6(src=ip_src, dst=ip_dst, hlim=ip_ttl) # Add IPv6 Extension Headers if EH: From 34c478ff0dcbbd39da6740a8e34d69e9fc40fcdd Mon Sep 17 00:00:00 2001 From: Rob Sherwood Date: Thu, 30 Jul 2015 10:46:54 -0700 Subject: [PATCH 09/12] OF-DPA ipv6 unicast forwarding now working Two caveats: * running the test back to back triggers a bug in the 'delete all groups' logic. Need to fix. * I think I observed this working most times but not always. I think it was an OF-DPA setup problem but could in theory be a race condition or other error with the test. TBD. --- tests-ofdpa-2.0/ipv6_fwd.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests-ofdpa-2.0/ipv6_fwd.py b/tests-ofdpa-2.0/ipv6_fwd.py index cd57ec77d..50f502160 100644 --- a/tests-ofdpa-2.0/ipv6_fwd.py +++ b/tests-ofdpa-2.0/ipv6_fwd.py @@ -12,6 +12,7 @@ import oftest.base_tests as base_tests import ofp import ofdpa_utils +import oftest.dataplane from oftest.parse import parse_ipv6 from oftest.testutils import * @@ -148,6 +149,7 @@ def runTest(self): continue # Generate the test and expected packets parsed_pkt = testutils12.simple_ipv6_packet( + ip_src = in_iface.dst_ip, ip_dst = out_iface.dst_ip, dl_dst = in_iface.mac) pkt = str(parsed_pkt) @@ -155,14 +157,18 @@ def runTest(self): parsed_expected_pkt = testutils12.simple_ipv6_packet( dl_src = out_iface.mac, dl_dst = out_iface.dst_mac, + ip_src = in_iface.dst_ip, ip_dst = out_iface.dst_ip, + ip_ttl = 63, # dl_vlan_enable=True, # vlan_vid=ofdpa_utils.DEFAULT_VLAN, # vlan_pcp=0, # pktlen=104 # 4 less than we started with, because the way simple_tcp calc's length ) expected_pkt = str(parsed_expected_pkt) - logging.info("IPv6_Untagged_Unicast test, iface %d to %d", ifaces[in_iface], ifaces[out_iface]) + logging.info("IPv6_Untagged_Unicast test, sending iface %d to %d", ifaces[in_iface], ifaces[out_iface]) + #logging.info("Expecting packet: %s" % parsed_expected_pkt.show2()) self.dataplane.send(in_iface.port, pkt) + oftest.dataplane.MATCH_VERBOSE=True verify_packets(self, expected_pkt, [out_iface.port]) From c1a5d212e50a4216491293f60d9327bbf49bc7ac Mon Sep 17 00:00:00 2001 From: Rob Sherwood Date: Mon, 3 Aug 2015 08:53:29 -0700 Subject: [PATCH 10/12] Pushing my code up for further review. Was working for OFDPA 2.0 EA1 but stops working on OFDPA 2.0 EA2 --- tests-ofdpa-2.0/ipv6_fwd.py | 153 ++++++++++++++++++++++++++++++--- tests-ofdpa-2.0/ofdpa_utils.py | 6 +- 2 files changed, 142 insertions(+), 17 deletions(-) diff --git a/tests-ofdpa-2.0/ipv6_fwd.py b/tests-ofdpa-2.0/ipv6_fwd.py index 50f502160..606728b5e 100644 --- a/tests-ofdpa-2.0/ipv6_fwd.py +++ b/tests-ofdpa-2.0/ipv6_fwd.py @@ -48,11 +48,11 @@ class IPv6_Untagged_Unicast(base_tests.SimpleDataPlane): """ def create_interface(self, iface): - l2_group_id = ofdpa_utils.makeGroupID("L2 Interface",(iface.vlan<<16) + iface.port) # bits 27:16 of the local_id are the vlan (!?) - l3_group_id = ofdpa_utils.makeGroupID("L3 Unicast",iface.port) + iface.l2_group_id = ofdpa_utils.makeGroupID("L2 Interface",(iface.vlan<<16) + iface.port) # bits 27:16 of the local_id are the vlan (!?) + iface.l3_group_id = ofdpa_utils.makeGroupID("L3 Unicast",iface.port) self.send_log_check("Creating IPv6 L2 interface group for port %d" % iface.port, ofp.message.group_add( group_type=ofp.OFPGT_ALL, - group_id=l2_group_id, + group_id=iface.l2_group_id, buckets=[ ofp.bucket( actions=[ @@ -81,11 +81,11 @@ def create_interface(self, iface): # Create the L3 unicast group entry self.send_log_check("Creating IPv6 Unicast group for %d" % iface.port, ofp.message.group_add( group_type=ofp.OFPGT_ALL, - group_id=l3_group_id, + group_id=iface.l3_group_id, buckets=[ ofp.bucket( actions=[ - ofp.action.group(l2_group_id), # chain to last L2 group; doesn't matter; will fix in test loop + ofp.action.group(iface.l2_group_id), # chain to this interface's L2 group, per OF-DPA spec ofp.action.set_field( ofp.oxm.eth_dst( macAddrToHexList(iface.dst_mac))), ofp.action.set_field( ofp.oxm.eth_src( macAddrToHexList(iface.mac))), ofp.action.set_field( ofp.oxm.vlan_vid( iface.vlan)), @@ -124,24 +124,39 @@ def send_log_check(self, log_msg, of_msg): def runTest(self): ports = sorted(config["port_map"].keys()) + # FIXME test to make sure we have at least 6 ports delete_all_flows(self.controller) - delete_all_groups(self.controller) + delete_all_groups(self.controller) # FIXME this fails to recursively delete groups do_barrier(self.controller) - ifaces = { + + ifaces = dict() + + # FIXME hack around potentially missing/non-contiguous ports + if 1 in ports: # mac,vlan,port, dst_mac, dst_ip - L3Iface('00:11:11:11:11:11', 1, 1, '00:11:11:11:11:ff', 'fe80::1111'): 1, - L3Iface('00:22:22:22:22:22', 2, 2, '00:22:22:22:22:ff', 'fe80::2222'): 2, - L3Iface('00:33:33:33:33:33', 3, 3, '00:33:33:33:33:ff', 'fe80::3333'): 3, - L3Iface('00:44:44:44:44:44', 4, 4, '00:44:44:44:44:ff', 'fe80::4444'): 4, - L3Iface('00:55:55:55:55:55', 5, 5, '00:55:55:55:55:ff', 'fe80::5555'): 5, - L3Iface('00:66:66:66:66:66', 6, 6, '00:66:66:66:66:ff', 'fe80::6666'): 6, - } + ifaces[L3Iface('00:11:11:11:11:11', 1, 1, '00:11:11:11:11:ff', 'fe80::1111')] = 1 + + if 2 in ports: + ifaces[L3Iface('00:22:22:22:22:22', 2, 2, '00:22:22:22:22:ff', 'fe80::2222')] = 2 + + if 3 in ports: + ifaces[L3Iface('00:33:33:33:33:33', 3, 3, '00:33:33:33:33:ff', 'fe80::3333')] = 3 + + if 4 in ports: + ifaces[L3Iface('00:44:44:44:44:44', 4, 4, '00:44:44:44:44:ff', 'fe80::4444')] = 4 + + if 5 in ports: + ifaces[L3Iface('00:55:55:55:55:55', 5, 5, '00:55:55:55:55:ff', 'fe80::5555')] = 5 + + if 6 in ports: + ifaces[L3Iface('00:66:66:66:66:66', 6, 6, '00:66:66:66:66:ff', 'fe80::6666')] = 6 for iface in ifaces: self.create_interface(iface) + # Store the match for the unicast match below # test sending from every inteface to every other interface for in_iface in sorted(ifaces): for out_iface in sorted(ifaces): @@ -172,3 +187,113 @@ def runTest(self): oftest.dataplane.MATCH_VERBOSE=True verify_packets(self, expected_pkt, [out_iface.port]) + + +@group('ipv6_fwd') +class IPv6_Untagged_ECMP(IPv6_Untagged_Unicast): + """ + Test routing function for an IPv6 IP packet with ECMP + + Send packets from first port and receive packets on any of second, third, forth ports + + Need to insert rules into: + 1) Ecmp group actions in the group table + 2) Vlan table to map packet to a vlan + 3) Mac Term table to send router's mac packets to the routing table + 4) the actual routing rule in the unicast routing table + + Then test from all ports to all ports + + """ + + + def runTest(self): + ports = sorted(config["port_map"].keys()) + + # FIXME test to make sure we have at least 6 ports + + delete_all_flows(self.controller) + delete_all_groups(self.controller) + do_barrier(self.controller) + + + ifaces = [ + # mac,vlan,port, dst_mac, dst_ip + L3Iface('00:11:11:11:11:11', 1, 1, '00:11:11:11:11:ff', 'fe80::1111'), + L3Iface('00:22:22:22:22:22', 2, 2, '00:22:22:22:22:ff', 'fe80::2222'), + L3Iface('00:33:33:33:33:33', 3, 3, '00:33:33:33:33:ff', 'fe80::3333'), + L3Iface('00:44:44:44:44:44', 4, 4, '00:44:44:44:44:ff', 'fe80::4444'), + #L3Iface('00:55:55:55:55:55', 5, 5, '00:55:55:55:55:ff', 'fe80::5555'), + #L3Iface('00:66:66:66:66:66', 6, 6, '00:66:66:66:66:ff', 'fe80::6666'), + ] + + for iface in ifaces: + self.create_interface(iface) + + ecmp_dst_ip = 'fe80::eeee' # stuff sent to this address should be split + # across the other interfaces + ecmp_dst_prefix = 'fe80::ee00' + ecmp_dst_mask = ':'.join(['ffff'] * 7) + ":ff00" # "ff:ff:ff:...:ff:00" + logging.info("ECMP prefix is %s/%s" % (ecmp_dst_prefix, ecmp_dst_mask)) + + ecmp_group_id = ofdpa_utils.makeGroupID("L3 ECMP", 1) + self.send_log_check("Creating L3 ECMP group", ofp.message.group_add( + group_type=ofp.OFPGT_SELECT, + group_id = ecmp_group_id, + buckets = + # make a list of the group ID's for ports 2-4 + map( (lambda iface : ofp.bucket( actions = [ofp.action.group(iface.l3_group_id) ] )), + ifaces[1:]) + )) + + self.send_log_check("Adding IPv6 route rule to ECMP output to ports 2-4" , ofp.message.flow_add( + table_id = ofdpa_utils.UNICAST_ROUTING_TABLE.table_id, + cookie=77, + match= ofp.match([ + ofp.oxm.eth_type(0x86dd), # ethertype = IPv6 + ofp.oxm.ipv6_dst_masked(parse_ipv6(ecmp_dst_prefix), parse_ipv6(ecmp_dst_mask)), + ]), + instructions=[ + ofp.instruction.goto_table(ofdpa_utils.ACL_TABLE.table_id), + ofp.instruction.write_actions([ + ofp.action.group(ecmp_group_id) + ]) + ], + buffer_id=ofp.OFP_NO_BUFFER, + priority=1000)) + + for iface in ifaces[1:]: + iface.count = 0 + + # test sending lots of packets from iface 0 to ifaces 1-3 + maxPackets = 20 + for src in range(maxPackets): + ip_src = 'fe80::01:%.4d' % src # give each packet a unique source + parsed_pkt = testutils12.simple_ipv6_packet( + ip_src = ip_src, + ip_dst = ecmp_dst_ip, + dl_dst = ifaces[0].mac) + pkt = str(parsed_pkt) + + logging.info("IPv6_Untagged_ECMP test, sending from iface %d to ECMP group", ifaces[0]) + self.dataplane.send(ifaces[0].port, pkt) + found = False + for iface in ifaces[1:]: + logging.debug("Checking for ECMP pkt on port %d", iface.port) + (rcv_port, rcv_pkt, pkt_time) = self.dataplane.poll(port_number=iface.port) + if rcv_pkt is not None: + parsed_rcv = oftest.parse.packet_to_flow_match(rcv_pkt) + if rcv_pkt.dl_dst == iface.dst_mac and \ + rcv_pkt.dl_src == iface.mac and \ + rcv_pkt.ip_dst == ecmp_dst_ip and \ + rcv_pkt.hlim == 63: # was TTL decremented? + found = True + iface.count += 1 + break + + test.assertTrue(found != None, "Did not receive pkt on Any ECMP interface") + # now make sure each interface got at least one packet; this is statistical so could fail if + # max packets is not high enough + for iface in ifaces[1:]: + test.assertTrue(iface.count > 0, "L3 interface %d did not receive any packets" % iface.port) + diff --git a/tests-ofdpa-2.0/ofdpa_utils.py b/tests-ofdpa-2.0/ofdpa_utils.py index 406515453..5454c4ef3 100644 --- a/tests-ofdpa-2.0/ofdpa_utils.py +++ b/tests-ofdpa-2.0/ofdpa_utils.py @@ -46,12 +46,12 @@ def enableVlanOnPort(controller, vlan, port=ofp.OFPP_ALL, priority=0): cookie = 0xdead, match = tagged_match, instructions = [ + ofp.instruction.goto_table(MACTERM_TABLE.table_id), ofp.instruction.apply_actions( actions=[ -# ofp.action.push_vlan(ethertype=0x8100), # DO NOT PUT THIS FOR OF-DPA - ofp.action.set_field(ofp.oxm.vlan_vid(vlan)) + ofp.action.push_vlan(ethertype=0x8100), # DO NOT PUT THIS FOR OF-DPA 2.0 EA1 - seems to not matter for EA2 + ofp.action.set_field(ofp.oxm.vlan_vid( ofp.OFPVID_PRESENT | vlan)) ]), - ofp.instruction.goto_table(MACTERM_TABLE.table_id) ], buffer_id = ofp.OFP_NO_BUFFER, priority = priority) From 99c8d027eebe18d9f8d41c2a2349fe082b81120e Mon Sep 17 00:00:00 2001 From: Rob Sherwood Date: Mon, 3 Aug 2015 23:28:22 -0700 Subject: [PATCH 11/12] Last second changes for OF-DPA 2.0 EA2 Also added a ipv6 tagged test. There is pathological code in the untagged VLAN case. --- tests-ofdpa-2.0/ipv6_fwd.py | 90 ++++++++++++++++++++++++++++++---- tests-ofdpa-2.0/ofdpa_utils.py | 19 ++++--- 2 files changed, 93 insertions(+), 16 deletions(-) diff --git a/tests-ofdpa-2.0/ipv6_fwd.py b/tests-ofdpa-2.0/ipv6_fwd.py index 606728b5e..80096d63f 100644 --- a/tests-ofdpa-2.0/ipv6_fwd.py +++ b/tests-ofdpa-2.0/ipv6_fwd.py @@ -47,18 +47,18 @@ class IPv6_Untagged_Unicast(base_tests.SimpleDataPlane): Then test from all ports to all ports """ - def create_interface(self, iface): + def create_interface(self, iface, untagged=True): iface.l2_group_id = ofdpa_utils.makeGroupID("L2 Interface",(iface.vlan<<16) + iface.port) # bits 27:16 of the local_id are the vlan (!?) iface.l3_group_id = ofdpa_utils.makeGroupID("L3 Unicast",iface.port) + actions = [ ofp.action.output(iface.port) ] + if untagged: + actions.append(ofp.action.pop_vlan()) + self.send_log_check("Creating IPv6 L2 interface group for port %d" % iface.port, ofp.message.group_add( group_type=ofp.OFPGT_ALL, group_id=iface.l2_group_id, buckets=[ - ofp.bucket( - actions=[ - ofp.action.pop_vlan(), - ofp.action.output(iface.port) - ]) + ofp.bucket( actions=actions) ])) # Insert a rule in the MAC termination table to send to Unicast router table @@ -99,7 +99,10 @@ def create_interface(self, iface): ]) # OF-DPA requires vlans to function - ofdpa_utils.installDefaultVlan(self.controller, iface.vlan, iface.port) + if untagged: + ofdpa_utils.installDefaultVlan(self.controller, iface.vlan, iface.port) + else: + ofdpa_utils.enableVlanOnPort(self.controller, iface.vlan, iface.port) self.send_log_check("Modifying IPv6 route rule to output to port %d" % iface.port, ofp.message.flow_modify( @@ -153,7 +156,7 @@ def runTest(self): if 6 in ports: ifaces[L3Iface('00:66:66:66:66:66', 6, 6, '00:66:66:66:66:ff', 'fe80::6666')] = 6 - for iface in ifaces: + for iface in sorted(ifaces): self.create_interface(iface) # Store the match for the unicast match below @@ -176,7 +179,7 @@ def runTest(self): ip_dst = out_iface.dst_ip, ip_ttl = 63, # dl_vlan_enable=True, -# vlan_vid=ofdpa_utils.DEFAULT_VLAN, +# dl_vlan=ofdpa_utils.DEFAULT_VLAN, # vlan_pcp=0, # pktlen=104 # 4 less than we started with, because the way simple_tcp calc's length ) @@ -189,6 +192,75 @@ def runTest(self): +@group('ipv6_fwd') +class IPv6_Tagged_Unicast(IPv6_Untagged_Unicast): + def runTest(self): + ports = sorted(config["port_map"].keys()) + # FIXME test to make sure we have at least 6 ports + + delete_all_flows(self.controller) + delete_all_groups(self.controller) # FIXME this fails to recursively delete groups + do_barrier(self.controller) + + + ifaces = dict() + + # FIXME hack around potentially missing/non-contiguous ports + if 1 in ports: + # mac,vlan,port, dst_mac, dst_ip + ifaces[L3Iface('00:11:11:11:11:11', 1, 1, '00:11:11:11:11:ff', 'fe80::1111')] = 1 + + if 2 in ports: + ifaces[L3Iface('00:22:22:22:22:22', 2, 2, '00:22:22:22:22:ff', 'fe80::2222')] = 2 + + if 3 in ports: + ifaces[L3Iface('00:33:33:33:33:33', 3, 3, '00:33:33:33:33:ff', 'fe80::3333')] = 3 + + if 4 in ports: + ifaces[L3Iface('00:44:44:44:44:44', 4, 4, '00:44:44:44:44:ff', 'fe80::4444')] = 4 + + if 5 in ports: + ifaces[L3Iface('00:55:55:55:55:55', 5, 5, '00:55:55:55:55:ff', 'fe80::5555')] = 5 + + if 6 in ports: + ifaces[L3Iface('00:66:66:66:66:66', 6, 6, '00:66:66:66:66:ff', 'fe80::6666')] = 6 + + for iface in ifaces: + self.create_interface(iface, untagged=False) + + # Store the match for the unicast match below + # test sending from every inteface to every other interface + for in_iface in sorted(ifaces): + for out_iface in sorted(ifaces): + if in_iface == out_iface: + continue + # Generate the test and expected packets + parsed_pkt = testutils12.simple_ipv6_packet( + ip_src = in_iface.dst_ip, + ip_dst = out_iface.dst_ip, + dl_vlan_enable=True, + dl_vlan = in_iface.vlan, + dl_dst = in_iface.mac) + pkt = str(parsed_pkt) + + parsed_expected_pkt = testutils12.simple_ipv6_packet( + dl_src = out_iface.mac, + dl_dst = out_iface.dst_mac, + ip_src = in_iface.dst_ip, + ip_dst = out_iface.dst_ip, + ip_ttl = 63, + dl_vlan_enable=True, + dl_vlan=out_iface.vlan +# pktlen=104 # 4 less than we started with, because the way simple_tcp calc's length + ) + expected_pkt = str(parsed_expected_pkt) + logging.info("IPv6_Tagged_Unicast test, sending iface %d to %d", ifaces[in_iface], ifaces[out_iface]) + #logging.info("Expecting packet: %s" % parsed_expected_pkt.show2()) + self.dataplane.send(in_iface.port, pkt) + oftest.dataplane.MATCH_VERBOSE=True + verify_packets(self, expected_pkt, [out_iface.port]) + + @group('ipv6_fwd') class IPv6_Untagged_ECMP(IPv6_Untagged_Unicast): """ diff --git a/tests-ofdpa-2.0/ofdpa_utils.py b/tests-ofdpa-2.0/ofdpa_utils.py index 5454c4ef3..ed5259027 100644 --- a/tests-ofdpa-2.0/ofdpa_utils.py +++ b/tests-ofdpa-2.0/ofdpa_utils.py @@ -47,11 +47,11 @@ def enableVlanOnPort(controller, vlan, port=ofp.OFPP_ALL, priority=0): match = tagged_match, instructions = [ ofp.instruction.goto_table(MACTERM_TABLE.table_id), - ofp.instruction.apply_actions( - actions=[ - ofp.action.push_vlan(ethertype=0x8100), # DO NOT PUT THIS FOR OF-DPA 2.0 EA1 - seems to not matter for EA2 - ofp.action.set_field(ofp.oxm.vlan_vid( ofp.OFPVID_PRESENT | vlan)) - ]), +# ofp.instruction.apply_actions( +# actions=[ +# ofp.action.push_vlan(ethertype=0x8100), # DO NOT PUT THIS FOR OF-DPA 2.0 EA1 - seems to not matter for EA2 +# ofp.action.set_field(ofp.oxm.vlan_vid( ofp.OFPVID_PRESENT | vlan)) +# ]), ], buffer_id = ofp.OFP_NO_BUFFER, priority = priority) @@ -90,7 +90,8 @@ def installDefaultVlan(controller, vlan=DEFAULT_VLAN, port=ofp.OFPP_ALL, priorit untagged_match = ofp.match([ ofp.oxm.in_port(port), - ofp.oxm.vlan_vid(0) + # OFDPA 2.0 says untagged is vlan_id=0, mask=ofp.OFPVID_PRESENT + ofp.oxm.vlan_vid_masked(0,ofp.OFPVID_PRESENT) # WTF OFDPA 2.0EA2 -- really!? ]) request = ofp.message.flow_add( @@ -100,7 +101,7 @@ def installDefaultVlan(controller, vlan=DEFAULT_VLAN, port=ofp.OFPP_ALL, priorit instructions = [ ofp.instruction.apply_actions( actions=[ -# ofp.action.push_vlan(ethertype=0x8100), + #ofp.action.push_vlan(ethertype=0x8100), ofp.action.set_field(ofp.oxm.vlan_vid(ofp.OFPVID_PRESENT | vlan)) ]), ofp.instruction.goto_table(MACTERM_TABLE.table_id) @@ -148,3 +149,7 @@ def makeGroupID(groupType, local_id): if local_id < 0 or local_id >=134217728: raise ValueError("local_id %d must be 0<= local_id < 2**27" % local_id) return (_group_types[groupType] << 28) + local_id + + +def delete_all_recursive_groups(controller): + pass From 3fe1605f43b2d4d62540fc4ce141e262153705bd Mon Sep 17 00:00:00 2001 From: Rob Sherwood Date: Wed, 30 Sep 2015 16:41:13 -0700 Subject: [PATCH 12/12] removed redundant group names --- tests-ofdpa-2.0/ipv6_fwd.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests-ofdpa-2.0/ipv6_fwd.py b/tests-ofdpa-2.0/ipv6_fwd.py index 80096d63f..2212d8833 100644 --- a/tests-ofdpa-2.0/ipv6_fwd.py +++ b/tests-ofdpa-2.0/ipv6_fwd.py @@ -33,7 +33,6 @@ def __init__(self, mac,vlan,port, dst_mac, dst_ip): self.dst_ip = dst_ip -@group('ipv6_fwd') class IPv6_Untagged_Unicast(base_tests.SimpleDataPlane): """ Test routing function for an IPv6 IP packet @@ -192,7 +191,6 @@ def runTest(self): -@group('ipv6_fwd') class IPv6_Tagged_Unicast(IPv6_Untagged_Unicast): def runTest(self): ports = sorted(config["port_map"].keys()) @@ -261,7 +259,6 @@ def runTest(self): verify_packets(self, expected_pkt, [out_iface.port]) -@group('ipv6_fwd') class IPv6_Untagged_ECMP(IPv6_Untagged_Unicast): """ Test routing function for an IPv6 IP packet with ECMP