Skip to content

Commit

Permalink
[SAIP4] Add basic support for recirculate action.
Browse files Browse the repository at this point in the history
  • Loading branch information
kheradmandG authored and divyagayathri-hcl committed Oct 15, 2024
1 parent 95a98a3 commit a8e7e6e
Show file tree
Hide file tree
Showing 13 changed files with 266 additions and 19 deletions.
1 change: 1 addition & 0 deletions sai_p4/fixed/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ filegroup(
"ingress_cloning.p4",
"ipv4_checksum.p4",
"l3_admit.p4",
"loopback.p4",
"metadata.p4",
"minimum_guaranteed_sizes.p4",
"mirroring.p4",
Expand Down
26 changes: 14 additions & 12 deletions sai_p4/fixed/ids.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 -----------------------------------------------------------------

Expand All @@ -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
Expand Down
48 changes: 48 additions & 0 deletions sai_p4/fixed/loopback.p4
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#ifndef SAI_LOOPBACK_P4_
#define SAI_LOOPBACK_P4_

#include <v1model.p4>
#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_
12 changes: 11 additions & 1 deletion sai_p4/fixed/metadata.p4
Original file line number Diff line number Diff line change
Expand Up @@ -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 ---------------------------------------------------------
Expand Down Expand Up @@ -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<PORT_BITWIDTH> 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
Expand Down
12 changes: 11 additions & 1 deletion sai_p4/fixed/parser.p4
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions sai_p4/fixed/roles.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_
5 changes: 4 additions & 1 deletion sai_p4/instantiations/google/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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 --------------------------------------------------------------------
Expand Down
17 changes: 17 additions & 0 deletions sai_p4/instantiations/google/sai_pd.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,9 @@ const absl::flat_hash_set<std::string>& 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",
Expand Down
10 changes: 10 additions & 0 deletions sai_p4/instantiations/google/test_tools/test_entries.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions sai_p4/instantiations/google/test_tools/test_entries.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_;
Expand Down
138 changes: 138 additions & 0 deletions sai_p4/instantiations/google/tests/sai_p4_bmv2_loopback_test.cc
Original file line number Diff line number Diff line change
@@ -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 <optional>
#include <vector>

#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<int, packetlib::Packets>;

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<packetlib::Packet>(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<p4::v1::Entity> 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<p4::v1::Entity> 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
Loading

0 comments on commit a8e7e6e

Please sign in to comment.