From a8e7e6e18f8c1e8bef386f769a2acaad0f12a478 Mon Sep 17 00:00:00 2001 From: kheradmandG Date: Thu, 10 Oct 2024 11:37:22 -0700 Subject: [PATCH] [SAIP4] Add basic support for recirculate action. --- sai_p4/fixed/BUILD.bazel | 1 + sai_p4/fixed/ids.h | 26 ++-- sai_p4/fixed/loopback.p4 | 48 ++++++ sai_p4/fixed/metadata.p4 | 12 +- sai_p4/fixed/parser.p4 | 12 +- sai_p4/fixed/roles.h | 6 + sai_p4/instantiations/google/BUILD.bazel | 5 +- sai_p4/instantiations/google/sai_pd.proto | 17 +++ .../test_tools/table_entry_generator.cc | 3 +- .../google/test_tools/test_entries.cc | 10 ++ .../google/test_tools/test_entries.h | 1 + .../google/tests/sai_p4_bmv2_loopback_test.cc | 138 ++++++++++++++++++ .../google/tests/sai_p4_bmv2_vlan_test.cc | 6 +- 13 files changed, 266 insertions(+), 19 deletions(-) create mode 100644 sai_p4/fixed/loopback.p4 create mode 100644 sai_p4/instantiations/google/tests/sai_p4_bmv2_loopback_test.cc diff --git a/sai_p4/fixed/BUILD.bazel b/sai_p4/fixed/BUILD.bazel index 23e64752..d7c55e9a 100644 --- a/sai_p4/fixed/BUILD.bazel +++ b/sai_p4/fixed/BUILD.bazel @@ -28,6 +28,7 @@ filegroup( "ingress_cloning.p4", "ipv4_checksum.p4", "l3_admit.p4", + "loopback.p4", "metadata.p4", "minimum_guaranteed_sizes.p4", "mirroring.p4", diff --git a/sai_p4/fixed/ids.h b/sai_p4/fixed/ids.h index c8825c41..f87e70fa 100644 --- a/sai_p4/fixed/ids.h +++ b/sai_p4/fixed/ids.h @@ -30,7 +30,8 @@ #define IPV6_TUNNEL_TERMINATION_TABLE_ID 0x0200004B // 33554507 #define DISABLE_VLAN_CHECKS_TABLE_ID 0x0200004D // 33554509 #define INGRESS_CLONE_TABLE_ID 0x02000051 // 33554513 -// Next available table id: 0x02000052 (33554514) +#define EGRESS_PORT_LOOPBACK_TABLE_ID 0x02000052 // 33554514 +// Next available table id: 0x02000053 (33554515) // --- Actions ----------------------------------------------------------------- @@ -57,17 +58,18 @@ #define CLONING_INGRESS_CLONE_ACTION_ID 0x0100001C // 16777244 #define CLONING_MIRROR_WITH_PSAMP_ENCAPSULATION_ACTION_ID \ 0x0100001D // 16777245 -#define L3_ADMIT_ACTION_ID 0x01000008 // 16777224 -#define MIRRORING_SET_PRE_SESSION_ACTION_ID 0x01000009 // 16777225 -#define SELECT_ECMP_HASH_ALGORITHM_ACTION_ID 0x010000A // 16777226 -#define COMPUTE_ECMP_HASH_IPV4_ACTION_ID 0x0100000B // 16777227 -#define COMPUTE_ECMP_HASH_IPV6_ACTION_ID 0x0100000C // 16777228 -#define COMPUTE_LAG_HASH_IPV4_ACTION_ID 0x0100000D // 16777229 -#define COMPUTE_LAG_HASH_IPV6_ACTION_ID 0x0100000E // 16777230 -#define ROUTING_SET_METADATA_AND_DROP_ACTION_ID 0x01000015 // 16777237 -#define MARK_FOR_TUNNEL_DECAP_AND_SET_VRF_ACTION_ID 0x01000016 // 16777238 -#define DISABLE_VLAN_CHECKS_ACTION_ID 0x0100001A // 16777242 -// Next available action id: 0x0100001E (16777246) +#define L3_ADMIT_ACTION_ID 0x01000008 // 16777224 +#define MIRRORING_SET_PRE_SESSION_ACTION_ID 0x01000009 // 16777225 +#define SELECT_ECMP_HASH_ALGORITHM_ACTION_ID 0x010000A // 16777226 +#define COMPUTE_ECMP_HASH_IPV4_ACTION_ID 0x0100000B // 16777227 +#define COMPUTE_ECMP_HASH_IPV6_ACTION_ID 0x0100000C // 16777228 +#define COMPUTE_LAG_HASH_IPV4_ACTION_ID 0x0100000D // 16777229 +#define COMPUTE_LAG_HASH_IPV6_ACTION_ID 0x0100000E // 16777230 +#define ROUTING_SET_METADATA_AND_DROP_ACTION_ID 0x01000015 // 16777237 +#define MARK_FOR_TUNNEL_DECAP_AND_SET_VRF_ACTION_ID 0x01000016 // 16777238 +#define DISABLE_VLAN_CHECKS_ACTION_ID 0x0100001A // 16777242 +#define EGRESS_LOOPBACK_ACTION_ID 0x0100001E // 16777246 +// Next available action id: 0x0100001F (16777247) // --- Action Profiles and Selectors (8 most significant bits = 0x11) ---------- // This value should ideally be 0x11000001, but we currently have this value for diff --git a/sai_p4/fixed/loopback.p4 b/sai_p4/fixed/loopback.p4 new file mode 100644 index 00000000..a86457b4 --- /dev/null +++ b/sai_p4/fixed/loopback.p4 @@ -0,0 +1,48 @@ +#ifndef SAI_LOOPBACK_P4_ +#define SAI_LOOPBACK_P4_ + +#include +#include "headers.p4" +#include "metadata.p4" +#include "ids.h" +#include "bmv2_intrinsics.h" + + +control egress_port_loopback(inout headers_t headers, + inout local_metadata_t local_metadata, + inout standard_metadata_t standard_metadata) { + @id(EGRESS_LOOPBACK_ACTION_ID) + action egress_loopback() { + local_metadata.loopback_port = standard_metadata.egress_port; + recirculate_preserving_field_list((bit<8>)PreservedFieldList.RECIRCULATE); + } + + // This table models SAI_PORT_LOOPBACK_MODE_MAC on front panel ports: an entry + // in this table matching on an `out_port` P indicates that a packet going + // out of P will be recirculated back as an input packet. + // This table is only used in v1model targets (e.g. BMv2). Note that in GPINS + // the loopback mode is configured via gNMI config for the port rather than + // P4. + @id(EGRESS_PORT_LOOPBACK_TABLE_ID) + @p4runtime_role(P4RUNTIME_ROLE_V1MODEL_AUXILIARY_CONTROLLER) + // TODO: Remove @unsupported once we can ignore this table + // in P4Info verification + @unsupported + table egress_port_loopback_table { + key = { + (port_id_t)standard_metadata.egress_port: exact + @id(1) @name("out_port"); + } + actions = { + @proto_id(1) egress_loopback; + @defaultonly NoAction; + } + const default_action = NoAction; + } + + apply { + egress_port_loopback_table.apply(); + } +} // control egress_port_loopback + +#endif // SAI_LOOPBACK_P4_ diff --git a/sai_p4/fixed/metadata.p4 b/sai_p4/fixed/metadata.p4 index aa47df03..6fe3449b 100644 --- a/sai_p4/fixed/metadata.p4 +++ b/sai_p4/fixed/metadata.p4 @@ -10,7 +10,8 @@ // The field list numbers used in @field_list annotations to identify the fields // that need to be preserved during clone/recirculation/etc. operations. enum bit<8> PreservedFieldList { - MIRROR_AND_PACKET_IN_COPY = 8w1 + MIRROR_AND_PACKET_IN_COPY = 8w1, + RECIRCULATE = 8w2 }; // -- Translated Types --------------------------------------------------------- @@ -225,6 +226,15 @@ struct local_metadata_t { mirror_session_id_t mirror_session_id; port_id_t mirror_egress_port; + // If a packet is send to a port that is designated as a loopback port, + // the port is stored in this field and reciculated (see loopback.p4). + // This field (which is preserved during recirculation) will be used to set + // the ingress port to the loopback port after recirculation (as opposed to + // our hardware targets, this does NOT happen by default in v1model targets + // such as BMv2). + @field_list(PreservedFieldList.RECIRCULATE) + bit loopback_port; + // Packet-in related fields, which we can't group into a struct, because BMv2 // doesn't support passing structs in clone3. // We model packet-in in SAI P4 by using the replication engine to make a diff --git a/sai_p4/fixed/parser.p4 b/sai_p4/fixed/parser.p4 index 02bc09a5..d6d8eef9 100644 --- a/sai_p4/fixed/parser.p4 +++ b/sai_p4/fixed/parser.p4 @@ -5,6 +5,7 @@ #include "headers.p4" #include "ids.h" #include "metadata.p4" +#include "bmv2_intrinsics.h" parser packet_parser(packet_in packet, out headers_t headers, inout local_metadata_t local_metadata, @@ -33,7 +34,6 @@ parser packet_parser(packet_in packet, out headers_t headers, local_metadata.mirror_session_id = 0; local_metadata.mirror_egress_port = 0; local_metadata.color = MeterColor_t.GREEN; - local_metadata.ingress_port = (port_id_t)standard_metadata.ingress_port; local_metadata.route_metadata = 0; local_metadata.bypass_ingress = false; local_metadata.wcmp_group_id_valid = false; @@ -43,6 +43,16 @@ parser packet_parser(packet_in packet, out headers_t headers, local_metadata.ipmc_table_hit = false; local_metadata.acl_drop = false; + // If a packet is recirculated, use `loopback_port` the packet was sent to + // (which is preserved during recirculation) as the ingress port. + // Otherwise, use `standard_metadata.ingress_port`. + if (standard_metadata.instance_type == PKT_INSTANCE_TYPE_RECIRC) { + local_metadata.ingress_port = (port_id_t)local_metadata.loopback_port; + } else { + local_metadata.ingress_port = (port_id_t)standard_metadata.ingress_port; + } + + transition select(standard_metadata.ingress_port) { SAI_P4_CPU_PORT: parse_packet_out_header; _ : parse_ethernet; diff --git a/sai_p4/fixed/roles.h b/sai_p4/fixed/roles.h index a6c6a47b..0da168b0 100644 --- a/sai_p4/fixed/roles.h +++ b/sai_p4/fixed/roles.h @@ -13,9 +13,15 @@ #define P4RUNTIME_ROLE_MIRRORING P4RUNTIME_ROLE_SDN_CONTROLLER #endif +// TODO: Use P4RUNTIME_ROLE_V1MODEL_AUXILIARY_CONTROLLER instead. #ifndef P4RUNTIME_ROLE_PACKET_REPLICATION_ENGINE #define P4RUNTIME_ROLE_PACKET_REPLICATION_ENGINE \ "packet_replication_engine_manager" #endif +#ifndef P4RUNTIME_ROLE_V1MODEL_AUXILIARY_CONTROLLER +#define P4RUNTIME_ROLE_V1MODEL_AUXILIARY_CONTROLLER \ + "v1model_auxiliary_controller" +#endif + #endif // SAI_ROLES_P4_ diff --git a/sai_p4/instantiations/google/BUILD.bazel b/sai_p4/instantiations/google/BUILD.bazel index 50ee1794..e7873be3 100644 --- a/sai_p4/instantiations/google/BUILD.bazel +++ b/sai_p4/instantiations/google/BUILD.bazel @@ -175,7 +175,10 @@ p4_pd_proto( # Do not access directly. Use checked in sai_pd.proto instead. out = "generated/sai_pd.proto", package = "sai", - roles = ["sdn_controller"], + roles = [ + "sdn_controller", + "v1model_auxiliary_controller", + ], ) # -- P4Info -------------------------------------------------------------------- diff --git a/sai_p4/instantiations/google/sai_pd.proto b/sai_p4/instantiations/google/sai_pd.proto index 12448963..1b879168 100755 --- a/sai_p4/instantiations/google/sai_pd.proto +++ b/sai_p4/instantiations/google/sai_pd.proto @@ -290,6 +290,19 @@ message TunnelTableEntry { bytes controller_metadata = 8; } +// CAUTION: This table is not (yet) supported. +message EgressPortLoopbackTableEntry { + message Match { + string out_port = 1; // exact match / Format::STRING + } + Match match = 1; + message Action { + EgressLoopbackAction egress_loopback = 1; + } + Action action = 2; + bytes controller_metadata = 8; +} + // Table entry restrictions: // ## Forbid using ether_type for IP packets (by convention, use is_ip* // instead). @@ -894,6 +907,8 @@ message MirrorWithPsampEncapsulationAction { string monitor_port = 1; // Format::STRING } +message EgressLoopbackAction {} + message SetVrfAction { // Refers to 'vrf_table.vrf_id'. string vrf_id = 1; // Format::STRING @@ -1002,6 +1017,8 @@ message TableEntry { // CAUTION: This table is not (yet) supported. Ipv6MulticastTableEntry ipv6_multicast_table_entry = 79; TunnelTableEntry tunnel_table_entry = 80; + // CAUTION: This table is not (yet) supported. + EgressPortLoopbackTableEntry egress_port_loopback_table_entry = 82; AclIngressTableEntry acl_ingress_table_entry = 256; AclPreIngressTableEntry acl_pre_ingress_table_entry = 257; AclWbbIngressTableEntry acl_wbb_ingress_table_entry = 259; diff --git a/sai_p4/instantiations/google/test_tools/table_entry_generator.cc b/sai_p4/instantiations/google/test_tools/table_entry_generator.cc index 2a3abeec..96498cba 100644 --- a/sai_p4/instantiations/google/test_tools/table_entry_generator.cc +++ b/sai_p4/instantiations/google/test_tools/table_entry_generator.cc @@ -270,8 +270,9 @@ const absl::flat_hash_set& KnownUnsupportedTables() { "tunnel_table", "nexthop_table", "wcmp_group_table", - // Logical table that is not supported by the switch. + // Logical tables that are not supported by the switch. "ingress_clone_table", + "egress_port_loopback_table", // TODO: Add support for this table once the switch // supports it. "acl_ingress_mirror_and_redirect_table", diff --git a/sai_p4/instantiations/google/test_tools/test_entries.cc b/sai_p4/instantiations/google/test_tools/test_entries.cc index 5a7b15b9..92bf311b 100644 --- a/sai_p4/instantiations/google/test_tools/test_entries.cc +++ b/sai_p4/instantiations/google/test_tools/test_entries.cc @@ -590,4 +590,14 @@ EntryBuilder& EntryBuilder::AddMarkToMirrorAclEntry( return *this; } +EntryBuilder& EntryBuilder::AddEgressPortLoopbackEntry( + absl::string_view out_port) { + sai::EgressPortLoopbackTableEntry& loopback_entry = + *entries_.add_entries()->mutable_egress_port_loopback_table_entry(); + loopback_entry.mutable_match()->set_out_port(out_port); + loopback_entry.mutable_action()->mutable_egress_loopback(); + + return *this; +} + } // namespace sai diff --git a/sai_p4/instantiations/google/test_tools/test_entries.h b/sai_p4/instantiations/google/test_tools/test_entries.h index 52595b63..cc4d92a5 100644 --- a/sai_p4/instantiations/google/test_tools/test_entries.h +++ b/sai_p4/instantiations/google/test_tools/test_entries.h @@ -199,6 +199,7 @@ class EntryBuilder { const MirrorAndRedirectMatchFields& match_fields = {}); EntryBuilder& AddMirrorSessionTableEntry(const MirrorSessionParams& params); EntryBuilder& AddMarkToMirrorAclEntry(const MarkToMirrorParams& params); + EntryBuilder& AddEgressPortLoopbackEntry(absl::string_view out_port); private: sai::TableEntries entries_; diff --git a/sai_p4/instantiations/google/tests/sai_p4_bmv2_loopback_test.cc b/sai_p4/instantiations/google/tests/sai_p4_bmv2_loopback_test.cc new file mode 100644 index 00000000..531efb1d --- /dev/null +++ b/sai_p4/instantiations/google/tests/sai_p4_bmv2_loopback_test.cc @@ -0,0 +1,138 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/log/check.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "gutil/testing.h" +#include "p4/v1/p4runtime.pb.h" +#include "p4_pdpi/ir.pb.h" +#include "p4_pdpi/p4_runtime_session_extras.h" +#include "p4_pdpi/packetlib/packetlib.h" +#include "p4_pdpi/packetlib/packetlib.pb.h" +#include "platforms/networking/p4/p4_infra/bmv2/bmv2.h" +#include "sai_p4/instantiations/google/instantiations.h" +#include "sai_p4/instantiations/google/sai_p4info.h" +#include "sai_p4/instantiations/google/sai_pd.pb.h" +#include "sai_p4/instantiations/google/test_tools/set_up_bmv2.h" +#include "sai_p4/instantiations/google/test_tools/test_entries.h" + +namespace pins { +namespace { + +using ::orion::p4::test::Bmv2; +using ::testing::ElementsAre; +using ::testing::Key; + +using PacketsByPort = absl::flat_hash_map; + +void PreparePacketOrDie(packetlib::Packet& packet) { + CHECK_OK(packetlib::PadPacketToMinimumSize(packet).status()); // Crash OK. + CHECK_OK( + packetlib::UpdateMissingComputedFields(packet).status()); // Crash OK. +} + +packetlib::Packet GetIpv4PacketOrDie() { + auto packet = gutil::ParseProtoOrDie(R"pb( + headers { + ethernet_header { + ethernet_destination: "02:03:04:05:06:07" + ethernet_source: "00:01:02:03:04:05" + ethertype: "0x0800" # IPv4 + } + } + headers { + ipv4_header { + version: "0x4" + ihl: "0x5" + dscp: "0x1c" + ecn: "0x0" + identification: "0x0000" + flags: "0x0" + fragment_offset: "0x0000" + ttl: "0x20" + protocol: "0xfe" + ipv4_source: "192.168.100.2" + ipv4_destination: "192.168.100.1" + } + } + payload: "Untagged IPv4 packet." + )pb"); + PreparePacketOrDie(packet); + return packet; +} + +TEST(ExperimentalTorLoopbackTest, PacketsSentToLoopbackPortsGetRecirculated) { + const sai::Instantiation kInstantiation = sai::Instantiation::kExperimentalTor; + const pdpi::IrP4Info kIrP4Info = sai::GetIrP4Info(kInstantiation); + ASSERT_OK_AND_ASSIGN(Bmv2 bmv2, sai::SetUpBmv2ForSaiP4(kInstantiation)); + + constexpr int kIngressPort = 4; + constexpr int kLoobackPort = 5; + constexpr int kEgressPort = 6; + constexpr absl::string_view kLoobackPortProto = "\005"; + constexpr absl::string_view kEgressPortProto = "\006"; + + constexpr absl::string_view kRedirectNexthopId = "redirect-nexthop"; + + { + // Install entries to send packets to kLoobackPort. + // For packets ingressing from the kLoobackPort, send them to kEgressPort. + // However, do NOT yet install the entry to make kLoobackPort a loopback + // port. + ASSERT_OK_AND_ASSIGN( + std::vector pi_entities, + sai::EntryBuilder() + .AddEntriesForwardingIpPacketsToGivenPort(kLoobackPortProto) + .AddIngressAclEntryRedirectingToNexthop( + kRedirectNexthopId, + /*in_port_match=*/kLoobackPortProto) + .AddNexthopRifNeighborEntries(kRedirectNexthopId, kEgressPortProto) + .LogPdEntries() + .GetDedupedPiEntities(kIrP4Info, /*allow_unsupported=*/true)); + ASSERT_OK(pdpi::InstallPiEntities(bmv2.P4RuntimeSession(), pi_entities)); + + // Inject a test packet to kIngressPort. + ASSERT_OK_AND_ASSIGN(PacketsByPort output_by_port, + bmv2.SendPacket(kIngressPort, GetIpv4PacketOrDie())); + // The packet must be forwarded to kLoobackPort. + ASSERT_THAT(output_by_port, ElementsAre(Key(kLoobackPort))); + } + + { + // Now install an entry to make kLoobackPort a loopback port. + ASSERT_OK_AND_ASSIGN( + std::vector pi_entities, + sai::EntryBuilder() + .AddEgressPortLoopbackEntry(kLoobackPortProto) + .LogPdEntries() + .GetDedupedPiEntities(kIrP4Info, /*allow_unsupported=*/true)); + ASSERT_OK(pdpi::InstallPiEntities(bmv2.P4RuntimeSession(), pi_entities)); + + // Inject a test packet to kIngressPort. + ASSERT_OK_AND_ASSIGN(PacketsByPort output_by_port, + bmv2.SendPacket(kIngressPort, GetIpv4PacketOrDie())); + // The packet must be (looped back and eventually) forwarded to kEgressPort. + ASSERT_THAT(output_by_port, ElementsAre(Key(kEgressPort))); + } +} + +} // namespace +} // namespace pins diff --git a/sai_p4/instantiations/google/tests/sai_p4_bmv2_vlan_test.cc b/sai_p4/instantiations/google/tests/sai_p4_bmv2_vlan_test.cc index ca4170e0..1553ce23 100644 --- a/sai_p4/instantiations/google/tests/sai_p4_bmv2_vlan_test.cc +++ b/sai_p4/instantiations/google/tests/sai_p4_bmv2_vlan_test.cc @@ -563,7 +563,7 @@ sai::TableEntries EntriesForwardingAndRewritingVlanInRifTable( TEST(VlanTest, SettingNonReservedVidInRifWithoutVlanChecksResultsInPacketWithThatId) { - const sai::Instantiation kInstantiation = sai::Instantiation::kExperimentalTor; + const sai::Instantiation kInstantiation = sai::Instantiation::kTor; const pdpi::IrP4Info kIrP4Info = sai::GetIrP4Info(kInstantiation); ASSERT_OK_AND_ASSIGN(Bmv2 bmv2, sai::SetUpBmv2ForSaiP4(kInstantiation)); @@ -624,7 +624,7 @@ TEST(VlanTest, } TEST(VlanTest, SettingNonReservedVidInRifWithVlanChecksResultsInDrop) { - const sai::Instantiation kInstantiation = sai::Instantiation::kExperimentalTor; + const sai::Instantiation kInstantiation = sai::Instantiation::kTor; const pdpi::IrP4Info kIrP4Info = sai::GetIrP4Info(kInstantiation); ASSERT_OK_AND_ASSIGN(Bmv2 bmv2, sai::SetUpBmv2ForSaiP4(kInstantiation)); @@ -661,7 +661,7 @@ TEST(VlanTest, SettingNonReservedVidInRifWithVlanChecksResultsInDrop) { } TEST(VlanTest, SettingVid4095InRifResultsOutputPacketWithNoVlanTag) { - const sai::Instantiation kInstantiation = sai::Instantiation::kExperimentalTor; + const sai::Instantiation kInstantiation = sai::Instantiation::kTor; const pdpi::IrP4Info kIrP4Info = sai::GetIrP4Info(kInstantiation); ASSERT_OK_AND_ASSIGN(Bmv2 bmv2, sai::SetUpBmv2ForSaiP4(kInstantiation));