|
| 1 | +// Copyright 2025 Google LLC |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// https://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | + |
| 15 | +#include "tests/forwarding/acl_feature_test.h" |
| 16 | + |
| 17 | +#include <memory> |
| 18 | +#include <optional> |
| 19 | +#include <string> |
| 20 | +#include <tuple> |
| 21 | +#include <vector> |
| 22 | + |
| 23 | +#include "absl/log/check.h" |
| 24 | +#include "absl/status/status.h" |
| 25 | +#include "absl/strings/string_view.h" |
| 26 | +#include "dvaas/dataplane_validation.h" |
| 27 | +#include "dvaas/test_vector.h" |
| 28 | +#include "dvaas/validation_result.h" |
| 29 | +#include "glog/logging.h" |
| 30 | +#include "gmock/gmock.h" |
| 31 | +#include "gtest/gtest.h" |
| 32 | +#include "gutil/status.h" // IWYU pragma: keep |
| 33 | +#include "gutil/status.h" |
| 34 | +#include "gutil/status_matchers.h" // IWYU pragma: keep |
| 35 | +#include "gutil/testing.h" |
| 36 | +#include "lib/gnmi/gnmi_helper.h" |
| 37 | +#include "net/google::protobuf/contrib/fixtures/proto-fixture-repository.h" |
| 38 | +#include "p4/v1/p4runtime.pb.h" |
| 39 | +#include "p4_pdpi/ir.h" |
| 40 | +#include "p4_pdpi/ir.pb.h" |
| 41 | +#include "p4_pdpi/p4_runtime_session.h" |
| 42 | +#include "p4_pdpi/p4_runtime_session_extras.h" |
| 43 | +#include "p4_pdpi/packetlib/packetlib.h" |
| 44 | +#include "p4_pdpi/packetlib/packetlib.pb.h" |
| 45 | +#include "p4_pdpi/pd.h" |
| 46 | +#include "sai_p4/instantiations/google/sai_pd.pb.h" |
| 47 | +#include "sai_p4/instantiations/google/test_tools/test_entries.h" |
| 48 | +#include "tests/lib/switch_test_setup_helpers.h" |
| 49 | +#include "thinkit/mirror_testbed.h" |
| 50 | + |
| 51 | +namespace pins_test { |
| 52 | +namespace { |
| 53 | + |
| 54 | +using ::google::protobuf::contrib::fixtures::ProtoFixtureRepository; |
| 55 | + |
| 56 | +packetlib::Packet ParsePacketAndFillInComputedFields( |
| 57 | + const ProtoFixtureRepository& repo, absl::string_view packet_pb) { |
| 58 | + packetlib::Packet packet = repo.ParseTextOrDie<packetlib::Packet>(packet_pb); |
| 59 | + CHECK_OK(packetlib::PadPacketToMinimumSize(packet)); |
| 60 | + CHECK_OK(packetlib::UpdateMissingComputedFields(packet)); |
| 61 | + return packet; |
| 62 | +} |
| 63 | + |
| 64 | +// Setup ingress ACL forward all packets. |
| 65 | +absl::Status SetUpIngressAclForwardingAllPackets( |
| 66 | + pdpi::P4RuntimeSession* p4_session, const pdpi::IrP4Info& ir_p4info) { |
| 67 | + sai::TableEntry pd_entry = gutil::ParseProtoOrDie<sai::TableEntry>( |
| 68 | + R"pb( |
| 69 | + acl_ingress_table_entry { |
| 70 | + match {} # Wildcard match. |
| 71 | + action { acl_forward {} } |
| 72 | + priority: 1 |
| 73 | + } |
| 74 | + )pb"); |
| 75 | + |
| 76 | + ASSIGN_OR_RETURN( |
| 77 | + const p4::v1::TableEntry pi_entry, |
| 78 | + pdpi::PartialPdTableEntryToPiTableEntry(ir_p4info, pd_entry)); |
| 79 | + return pdpi::InstallPiTableEntry(p4_session, pi_entry); |
| 80 | +} |
| 81 | + |
| 82 | +// Helper function to build a UDP packet |
| 83 | +dvaas::PacketTestVector UdpPacket(std::string control_port, |
| 84 | + absl::string_view dst_mac, |
| 85 | + absl::string_view dst_ip, |
| 86 | + std::optional<sai::PuntAction> punt_action) { |
| 87 | + ProtoFixtureRepository repo; |
| 88 | + |
| 89 | + repo.RegisterValue("@payload", dvaas::MakeTestPacketTagFromUniqueId(1)) |
| 90 | + .RegisterValue("@ingress_port", control_port) |
| 91 | + .RegisterValue("@egress_port", control_port) |
| 92 | + .RegisterValue("@dst_ip", dst_ip) |
| 93 | + .RegisterValue("@dst_mac", dst_mac) |
| 94 | + .RegisterValue("@ttl", "0x10") |
| 95 | + .RegisterValue("@decremented_ttl", "0x0f"); |
| 96 | + |
| 97 | + dvaas::PacketTestVector test_vector = |
| 98 | + repo.RegisterSnippetOrDie<packetlib::Header>("@ethernet", R"pb( |
| 99 | + ethernet_header { |
| 100 | + ethernet_destination: @dst_mac, |
| 101 | + ethernet_source: "00:00:22:22:00:00" |
| 102 | + ethertype: "0x0800" # Udp |
| 103 | + } |
| 104 | + )pb") |
| 105 | + .RegisterSnippetOrDie<packetlib::Header>("@ipv4", R"pb( |
| 106 | + ipv4_header { |
| 107 | + version: "0x4" |
| 108 | + dscp: "0x1b" |
| 109 | + ecn: "0x1" |
| 110 | + ihl: "0x5" |
| 111 | + identification: "0x0000" |
| 112 | + flags: "0x0" |
| 113 | + ttl: @ttl |
| 114 | + fragment_offset: "0x0000" |
| 115 | + # payload_length: filled in automatically. |
| 116 | + protocol: "0x11" |
| 117 | + ipv4_source: "10.0.0.8" |
| 118 | + ipv4_destination: @dst_ip |
| 119 | + } |
| 120 | + )pb") |
| 121 | + .RegisterSnippetOrDie<packetlib::Header>("@udp", R"pb( |
| 122 | + udp_header { source_port: "0x0014" destination_port: "0x000a" } |
| 123 | + )pb") |
| 124 | + .RegisterMessage("@input_packet", ParsePacketAndFillInComputedFields( |
| 125 | + repo, |
| 126 | + R"pb( |
| 127 | + headers: @ethernet |
| 128 | + headers: @ipv4 |
| 129 | + headers: @udp |
| 130 | + payload: @payload |
| 131 | + )pb")) |
| 132 | + .RegisterMessage( |
| 133 | + "@output_packet", ParsePacketAndFillInComputedFields(repo, R"pb( |
| 134 | + headers: @ethernet { |
| 135 | + ethernet_header { |
| 136 | + ethernet_destination: "02:03:04:05:06:07" |
| 137 | + ethernet_source: "00:01:02:03:04:05" |
| 138 | + } |
| 139 | + } |
| 140 | + headers: @ipv4 { ipv4_header { ttl: @decremented_ttl } } |
| 141 | + headers: @udp |
| 142 | + payload: @payload |
| 143 | + )pb")) |
| 144 | + .ParseTextOrDie<dvaas::PacketTestVector>(R"pb( |
| 145 | + input { |
| 146 | + type: DATAPLANE |
| 147 | + packet { port: @ingress_port parsed: @input_packet } |
| 148 | + } |
| 149 | + acceptable_outputs { |
| 150 | + packets { port: @egress_port parsed: @output_packet } |
| 151 | + packet_ins { parsed: @output_packet } |
| 152 | + } |
| 153 | + )pb"); |
| 154 | + |
| 155 | + for (dvaas::SwitchOutput& output : |
| 156 | + *test_vector.mutable_acceptable_outputs()) { |
| 157 | + if (!punt_action.has_value()) { |
| 158 | + output.clear_packet_ins(); |
| 159 | + } else if (punt_action.value() == sai::PuntAction::kTrap) { |
| 160 | + output.clear_packets(); |
| 161 | + } |
| 162 | + } |
| 163 | + return test_vector; |
| 164 | +} |
| 165 | + |
| 166 | +// Helper routine to install L3 route |
| 167 | +absl::Status InstallL3Route(pdpi::P4RuntimeSession* switch_session, |
| 168 | + pdpi::IrP4Info ir_p4info, std::string given_port, |
| 169 | + std::optional<sai::PuntAction> punt_action) { |
| 170 | + std::vector<p4::v1::Entity> pi_entities; |
| 171 | + LOG(INFO) << "Installing L3 route"; |
| 172 | + |
| 173 | + sai::EntryBuilder entry_builder = |
| 174 | + sai::EntryBuilder() |
| 175 | + .AddVrfEntry("vrf-1") |
| 176 | + .AddPreIngressAclEntryAssigningVrfForGivenIpType( |
| 177 | + "vrf-1", sai::IpVersion::kIpv4) |
| 178 | + .AddDefaultRouteForwardingAllPacketsToGivenPort( |
| 179 | + given_port, sai::IpVersion::kIpv4, "vrf-1") |
| 180 | + .AddEntryAdmittingAllPacketsToL3(); |
| 181 | + |
| 182 | + if (punt_action.has_value()) { |
| 183 | + entry_builder.AddEntryPuntingAllPackets(punt_action.value()); |
| 184 | + } else { |
| 185 | + RETURN_IF_ERROR( |
| 186 | + SetUpIngressAclForwardingAllPackets(switch_session, ir_p4info)); |
| 187 | + } |
| 188 | + ASSIGN_OR_RETURN( |
| 189 | + pi_entities, |
| 190 | + entry_builder.LogPdEntries().GetDedupedPiEntities(ir_p4info)); |
| 191 | + RETURN_IF_ERROR(pdpi::InstallPiEntities(*switch_session, pi_entities)); |
| 192 | + return absl::OkStatus(); |
| 193 | +} |
| 194 | + |
| 195 | +TEST_P(AclFeatureTestFixture, AclDenyAction) { |
| 196 | + const AclFeatureTestParams& params = GetParam(); |
| 197 | + |
| 198 | + thinkit::MirrorTestbed& testbed = |
| 199 | + GetParam().mirror_testbed->GetMirrorTestbed(); |
| 200 | + std::unique_ptr<pdpi::P4RuntimeSession> sut_p4rt_session, |
| 201 | + control_switch_p4rt_session; |
| 202 | + |
| 203 | + ASSERT_OK_AND_ASSIGN( |
| 204 | + std::tie(sut_p4rt_session, control_switch_p4rt_session), |
| 205 | + pins_test::ConfigureSwitchPairAndReturnP4RuntimeSessionPair( |
| 206 | + testbed.Sut(), testbed.ControlSwitch(), std::nullopt, |
| 207 | + GetParam().p4info)); |
| 208 | + |
| 209 | + // Initialize the connection, clear table entries, and push GNMI |
| 210 | + // configuration (if given) for the SUT and Control switch. |
| 211 | + ASSERT_NE(sut_p4rt_session, nullptr); |
| 212 | + ASSERT_OK_AND_ASSIGN( |
| 213 | + p4::v1::GetForwardingPipelineConfigResponse sut_config, |
| 214 | + pdpi::GetForwardingPipelineConfig(sut_p4rt_session.get())); |
| 215 | + ASSERT_OK(testbed.Environment().StoreTestArtifact( |
| 216 | + "sut_p4Info.textproto", sut_config.config().p4info().DebugString())); |
| 217 | + ASSERT_OK_AND_ASSIGN(pdpi::IrP4Info sut_ir_p4info, |
| 218 | + pdpi::CreateIrP4Info(sut_config.config().p4info())); |
| 219 | + |
| 220 | + ASSERT_OK(pdpi::ClearTableEntries(sut_p4rt_session.get())); |
| 221 | + |
| 222 | + // Get control ports to test on. |
| 223 | + ASSERT_OK_AND_ASSIGN( |
| 224 | + auto gnmi_stub_control, |
| 225 | + GetParam().mirror_testbed->GetMirrorTestbed().Sut().CreateGnmiStub()); |
| 226 | + ASSERT_OK_AND_ASSIGN(std::string control_port, |
| 227 | + pins_test::GetAnyUpInterfacePortId(*gnmi_stub_control)); |
| 228 | + |
| 229 | + ASSERT_OK(InstallL3Route(sut_p4rt_session.get(), sut_ir_p4info, control_port, |
| 230 | + params.punt_action)); |
| 231 | + |
| 232 | + // remove the skip |
| 233 | + if (params.punt_action.has_value()) { |
| 234 | + // Run test with custom packet test vector. |
| 235 | + dvaas::DataplaneValidationParams dvaas_params = params.dvaas_params; |
| 236 | + dvaas_params.packet_test_vector_override = { |
| 237 | + UdpPacket(control_port, /*dst_mac=*/"00:aa:bb:cc:cc:dd", |
| 238 | + /*dst_ip=*/"10.0.0.1", params.punt_action)}; |
| 239 | + ASSERT_OK_AND_ASSIGN( |
| 240 | + dvaas::ValidationResult validation_result, |
| 241 | + GetParam().dvaas->ValidateDataplane(testbed, dvaas_params)); |
| 242 | + |
| 243 | + // Log statistics and check that things succeeded. |
| 244 | + validation_result.LogStatistics(); |
| 245 | + EXPECT_OK(validation_result.HasSuccessRateOfAtLeast(1.0)); |
| 246 | + } |
| 247 | +} |
| 248 | + |
| 249 | +} // namespace |
| 250 | +} // namespace pins_test |
0 commit comments