Skip to content

Commit e6e8658

Browse files
kishanpsdivyagayathri-hcl
authored andcommitted
[Thinkit/Tests] Adding packet_capture_test.cc file.
1 parent f0a5d7f commit e6e8658

File tree

2 files changed

+347
-0
lines changed

2 files changed

+347
-0
lines changed

tests/packet_capture/BUILD.bazel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ package(
2020
cc_library(
2121
name = "packet_capture_test",
2222
testonly = True,
23+
srcs = [
24+
"packet_capture_test.cc",
25+
],
2326
hdrs = [
2427
"packet_capture_test.h",
2528
],
Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
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+
// http://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/packet_capture/packet_capture_test.h"
16+
17+
#include <cstdint>
18+
#include <memory>
19+
#include <optional>
20+
#include <string>
21+
#include <vector>
22+
23+
#include "absl/container/flat_hash_map.h"
24+
#include "absl/flags/declare.h"
25+
#include "absl/status/statusor.h"
26+
#include "absl/strings/numbers.h"
27+
#include "absl/strings/str_cat.h"
28+
#include "absl/strings/string_view.h"
29+
#include "absl/time/clock.h"
30+
#include "absl/time/time.h"
31+
#include "absl/types/optional.h"
32+
#include "glog/logging.h"
33+
#include "gutil/collections.h"
34+
#include "gutil/status.h"
35+
#include "lib/gnmi/gnmi_helper.h"
36+
#include "lib/gnmi/openconfig.pb.h"
37+
#include "p4/config/v1/p4info.pb.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/string_encodings/hex_string.h"
46+
#include "proto/gnmi/gnmi.pb.h"
47+
#include "sai_p4/instantiations/google/instantiations.h"
48+
#include "sai_p4/instantiations/google/sai_pd.pb.h"
49+
#include "sai_p4/instantiations/google/test_tools/test_entries.h"
50+
#include "tests/forwarding/util.h"
51+
#include "tests/lib/switch_test_setup_helpers.h"
52+
#include "tests/packet_capture/packet_capture_test_util.h"
53+
#include "thinkit/mirror_testbed.h"
54+
#include "thinkit/proto/generic_testbed.pb.h"
55+
#include "thinkit/switch.h"
56+
#include "gmock/gmock.h"
57+
#include "gtest/gtest.h"
58+
59+
ABSL_DECLARE_FLAG(std::optional<sai::Instantiation>, switch_instantiation);
60+
61+
namespace pins_test {
62+
namespace {
63+
64+
using ::p4::config::v1::P4Info;
65+
using pctutil::SutToControlLink;
66+
67+
// Returns a set of table entries that will cause a switch to mirror all packets
68+
// on an incoming port to a mirror-to-port using PSAMP encapsulation and
69+
// adding a specified Vlan tag.
70+
absl::StatusOr<std::vector<p4::v1::Entity>>
71+
ConstructEntriesToMirrorTrafficWithVlanTag(
72+
const pdpi::IrP4Info &ir_p4info, const std::string &p4rt_src_port_id,
73+
const sai::MirrorSessionParams &mirror_session) {
74+
ASSIGN_OR_RETURN(
75+
std::vector<p4::v1::Entity> pi_entities,
76+
sai::EntryBuilder()
77+
.AddDisableVlanChecksEntry()
78+
.AddMirrorSessionTableEntry(mirror_session)
79+
.AddMarkToMirrorAclEntry(sai::MarkToMirrorParams{
80+
.ingress_port = p4rt_src_port_id,
81+
.mirror_session_id = mirror_session.mirror_session_id,
82+
})
83+
.GetDedupedPiEntities(ir_p4info));
84+
85+
return pi_entities;
86+
}
87+
88+
TEST_P(PacketCaptureTestWithoutIxia, PsampEncapsulatedMirroringTest) {
89+
LOG(INFO) << "-- START OF TEST ---------------------------------------------";
90+
Testbed().Environment().SetTestCaseID("TBD");
91+
92+
// Setup: the testbed consists of a SUT connected to a control device
93+
// that allows us to send and receive packets to/from the SUT.
94+
thinkit::Switch &sut = Testbed().Sut();
95+
thinkit::Switch &control_device = Testbed().ControlSwitch();
96+
97+
// Configure mirror testbed.
98+
std::unique_ptr<pdpi::P4RuntimeSession> sut_p4rt_session,
99+
control_p4rt_session;
100+
ASSERT_OK_AND_ASSIGN(sut_p4rt_session,
101+
pins_test::ConfigureSwitchAndReturnP4RuntimeSession(
102+
sut, std::nullopt, std::nullopt));
103+
104+
ASSERT_OK_AND_ASSIGN(control_p4rt_session,
105+
pins_test::ConfigureSwitchAndReturnP4RuntimeSession(
106+
control_device, std::nullopt, std::nullopt));
107+
108+
ASSERT_OK_AND_ASSIGN(const P4Info &p4info,
109+
pdpi::GetP4Info(*sut_p4rt_session));
110+
ASSERT_OK_AND_ASSIGN(const P4Info &control_p4info,
111+
pdpi::GetP4Info(*control_p4rt_session));
112+
ASSERT_OK_AND_ASSIGN(const pdpi::IrP4Info ir_p4info,
113+
pdpi::CreateIrP4Info(p4info));
114+
ASSERT_OK_AND_ASSIGN(const pdpi::IrP4Info ir_control_p4info,
115+
pdpi::CreateIrP4Info(control_p4info));
116+
// Store P4Info for debugging purposes.
117+
EXPECT_OK(
118+
Testbed().Environment().StoreTestArtifact("p4info.textproto", p4info));
119+
// Store gNMI config for debugging purposes.
120+
ASSERT_OK_AND_ASSIGN(auto sut_gnmi_stub, sut.CreateGnmiStub());
121+
ASSERT_OK_AND_ASSIGN(std::string sut_gnmi_config,
122+
pins_test::GetGnmiConfig(*sut_gnmi_stub));
123+
EXPECT_OK(Testbed().Environment().StoreTestArtifact("sut_gnmi_config.json",
124+
sut_gnmi_config));
125+
126+
ASSERT_OK_AND_ASSIGN(std::vector<p4::v1::Entity> pi_entities,
127+
sai::EntryBuilder()
128+
.AddEntryPuntingAllPackets(sai::PuntAction::kCopy)
129+
.GetDedupedPiEntities(ir_control_p4info));
130+
131+
ASSERT_OK(pdpi::InstallPiEntities(control_p4rt_session.get(),
132+
ir_control_p4info, pi_entities));
133+
134+
// Pick links to be used for packet injection and mirroring.
135+
ASSERT_OK_AND_ASSIGN(SutToControlLink link_used_for_test_packets,
136+
pctutil::PickSutToControlDeviceLinkThatsUp(Testbed()));
137+
LOG(INFO) << "Link used to inject test packets: "
138+
<< link_used_for_test_packets;
139+
140+
// Get P4RT IDs for SUT ports.
141+
absl::flat_hash_map<std::string, std::string> p4rt_id_by_interface;
142+
ASSERT_OK_AND_ASSIGN(p4rt_id_by_interface,
143+
GetAllInterfaceNameToPortId(*sut_gnmi_stub));
144+
ASSERT_OK_AND_ASSIGN(
145+
const std::string kSutIngressPortP4rtId,
146+
gutil::FindOrStatus(
147+
p4rt_id_by_interface,
148+
link_used_for_test_packets.sut_ingress_port_gnmi_name));
149+
ASSERT_OK_AND_ASSIGN(
150+
const std::string kSutEgressPortP4rtId,
151+
gutil::FindOrStatus(p4rt_id_by_interface,
152+
link_used_for_test_packets.sut_mtp_port_gnmi_name));
153+
// Get P4RT IDs for Control Switch ports.
154+
ASSERT_OK_AND_ASSIGN(auto control_gnmi_stub, control_device.CreateGnmiStub());
155+
ASSERT_OK_AND_ASSIGN(p4rt_id_by_interface,
156+
GetAllInterfaceNameToPortId(*control_gnmi_stub));
157+
ASSERT_OK_AND_ASSIGN(
158+
const std::string kControlSwitchInjectPortP4rtId,
159+
gutil::FindOrStatus(
160+
p4rt_id_by_interface,
161+
link_used_for_test_packets.control_switch_inject_port_gnmi_name));
162+
163+
// Configure mirror session attributes.
164+
auto mirror_session_params = sai::MirrorSessionParams{
165+
.mirror_session_id = "psamp_mirror",
166+
.monitor_port = kSutEgressPortP4rtId,
167+
.mirror_encap_src_mac = "00:00:00:22:22:22",
168+
.mirror_encap_dst_mac = "00:00:00:44:44:44",
169+
.mirror_encap_vlan_id = "0x0fe",
170+
.mirror_encap_src_ip = "2222:2222:2222:2222:2222:2222:2222:2222",
171+
.mirror_encap_dst_ip = "4444:4444:4444:4444:4444:4444:4444:4444",
172+
.mirror_encap_udp_src_port = "0x08ae",
173+
.mirror_encap_udp_dst_port = "0x1283"};
174+
// Install ACL table entry to match on inject port on Control switch
175+
// and mirror-to-port on SUT.
176+
ASSERT_OK_AND_ASSIGN(auto entries, ConstructEntriesToMirrorTrafficWithVlanTag(
177+
ir_p4info, kSutIngressPortP4rtId,
178+
mirror_session_params));
179+
ASSERT_OK(
180+
pdpi::InstallPiEntities(sut_p4rt_session.get(), ir_p4info, entries));
181+
182+
LOG(INFO) << "injecting test packet: "
183+
<< GetParam().test_packet.DebugString();
184+
ASSERT_OK_AND_ASSIGN(std::string raw_packet,
185+
packetlib::SerializePacket(GetParam().test_packet));
186+
187+
// Read ingress and egress port stat counters before packet injection.
188+
ASSERT_OK_AND_ASSIGN(
189+
uint64_t out_packets_pre,
190+
pctutil::GetGnmiStat("out-unicast-pkts",
191+
link_used_for_test_packets.sut_mtp_port_gnmi_name,
192+
sut_gnmi_stub.get()));
193+
ASSERT_OK_AND_ASSIGN(
194+
uint64_t in_packets_pre,
195+
pctutil::GetGnmiStat(
196+
"in-unicast-pkts",
197+
link_used_for_test_packets.sut_ingress_port_gnmi_name,
198+
sut_gnmi_stub.get()));
199+
200+
const int kPacketCount = 100;
201+
const int kPacketInjectDelayMs = 50;
202+
for (int i = 0; i < kPacketCount; ++i) {
203+
LOG(INFO) << "Injecting packet at time: " << absl::Now();
204+
ASSERT_OK(pins::InjectEgressPacket(
205+
/*port=*/kControlSwitchInjectPortP4rtId,
206+
/*packet=*/raw_packet,
207+
/*p4info=*/ir_p4info,
208+
/*p4rt=*/control_p4rt_session.get(),
209+
/*packet_delay=std::nullopt*/
210+
absl::Milliseconds(kPacketInjectDelayMs)));
211+
}
212+
// Read packets mirrored back to Control switch.
213+
std::vector<packetlib::Packet> received_packets;
214+
EXPECT_OK(control_p4rt_session->HandleNextNStreamMessages(
215+
[&](const p4::v1::StreamMessageResponse &message) {
216+
if (!message.has_packet())
217+
return false;
218+
packetlib::Packet received_packet =
219+
packetlib::ParsePacket(message.packet().payload());
220+
received_packets.push_back(received_packet);
221+
return true;
222+
},
223+
kPacketCount, absl::Minutes(2)));
224+
// Ensure the correct number of packets was received.
225+
ASSERT_EQ(received_packets.size(), kPacketCount);
226+
227+
// Validate headers of each received packet.
228+
int mirrored_packets_received = 0;
229+
std::optional<uint64_t> prev_obs_time, curr_obs_time;
230+
std::optional<int> prev_sequence, curr_sequence;
231+
for (const packetlib::Packet &received_packet : received_packets) {
232+
// Header count sanity check.
233+
LOG(INFO) << absl::StrCat("Packet: ", received_packet.DebugString());
234+
if (received_packet.headers().size() != 6) {
235+
continue;
236+
}
237+
// Parse Ethernet header.
238+
ASSERT_EQ(received_packet.headers(0).has_ethernet_header(), true);
239+
const auto &eth_header = received_packet.headers(0).ethernet_header();
240+
EXPECT_EQ(eth_header.ethernet_source(),
241+
mirror_session_params.mirror_encap_src_mac);
242+
EXPECT_EQ(eth_header.ethernet_destination(),
243+
mirror_session_params.mirror_encap_dst_mac);
244+
// Parse VLAN header.
245+
ASSERT_EQ(received_packet.headers(1).has_vlan_header(), true);
246+
// Parse IPv6 header.
247+
ASSERT_EQ(received_packet.headers(2).has_ipv6_header(), true);
248+
const auto &ip_header = received_packet.headers(2).ipv6_header();
249+
EXPECT_EQ(ip_header.ipv6_source(),
250+
mirror_session_params.mirror_encap_src_ip);
251+
EXPECT_EQ(ip_header.ipv6_destination(),
252+
mirror_session_params.mirror_encap_dst_ip);
253+
// Parse UDP header.
254+
ASSERT_EQ(received_packet.headers(3).has_udp_header(), true);
255+
const auto &udp_header = received_packet.headers(3).udp_header();
256+
257+
EXPECT_EQ(udp_header.source_port(),
258+
mirror_session_params.mirror_encap_udp_src_port);
259+
EXPECT_EQ(udp_header.destination_port(),
260+
mirror_session_params.mirror_encap_udp_dst_port);
261+
// Parse IPFIX header.
262+
ASSERT_EQ(received_packet.headers(4).has_ipfix_header(), true);
263+
// Validate sequence number incrementation.
264+
265+
ASSERT_OK_AND_ASSIGN(
266+
curr_sequence,
267+
pdpi::HexStringToInt(
268+
received_packet.headers(4).ipfix_header().sequence_number()));
269+
if (prev_sequence.has_value()) {
270+
EXPECT_EQ(curr_sequence, prev_sequence.value() + 1);
271+
}
272+
prev_sequence = curr_sequence;
273+
// Parse PSAMP header.
274+
ASSERT_EQ(received_packet.headers(5).has_psamp_header(), true);
275+
const auto &psamp_header = received_packet.headers(5).psamp_header();
276+
// Validate observation times increment within expected range.
277+
ASSERT_OK_AND_ASSIGN(curr_obs_time, pdpi::HexStringToUint64(
278+
psamp_header.observation_time()));
279+
if (prev_obs_time.has_value()) {
280+
constexpr int kObsTimeGapThresholdNs = 2000000;
281+
EXPECT_LE(
282+
(pctutil::ParsePsampHeaderObservationTime(curr_obs_time.value()) -
283+
pctutil::ParsePsampHeaderObservationTime(prev_obs_time.value())),
284+
absl::Nanoseconds(
285+
/*Injection delay*/ (kPacketInjectDelayMs * 1000000) +
286+
/*2 msecs*/ kObsTimeGapThresholdNs));
287+
}
288+
prev_obs_time = curr_obs_time;
289+
// Verify ingress port in PSAMP header.
290+
ASSERT_OK_AND_ASSIGN(
291+
auto ingress_vendor_port_id,
292+
pctutil::GetVendorPortId(
293+
link_used_for_test_packets.sut_ingress_port_gnmi_name,
294+
sut_gnmi_stub.get()));
295+
int gnmi_vendor_port_id = -1;
296+
ASSERT_TRUE(absl::SimpleAtoi(ingress_vendor_port_id, &gnmi_vendor_port_id));
297+
ASSERT_OK_AND_ASSIGN(auto psamp_ingress_port_id,
298+
pdpi::HexStringToInt(psamp_header.ingress_port()));
299+
EXPECT_EQ(psamp_ingress_port_id, gnmi_vendor_port_id);
300+
301+
LOG(INFO) << absl::StrCat(
302+
"Ingress port: ", psamp_header.ingress_port(),
303+
", Egress port: ", psamp_header.egress_port(),
304+
", Observation time: ", psamp_header.observation_time());
305+
mirrored_packets_received++;
306+
}
307+
308+
// Ensure at least 90% of packets was received after being mirrored.
309+
// Some packets in the 100 packets received could have been spurious.
310+
ASSERT_GE(mirrored_packets_received, kPacketCount * 0.9);
311+
// Check ingress and egress port counters increase as expected. That is,
312+
// within 100-110% of number of packets.
313+
ASSERT_OK_AND_ASSIGN(
314+
uint64_t in_packets_post,
315+
pctutil::GetGnmiStat(
316+
"in-unicast-pkts",
317+
link_used_for_test_packets.sut_ingress_port_gnmi_name,
318+
sut_gnmi_stub.get()));
319+
// Counter should increment by at least as many packets as were sent.
320+
EXPECT_GE(in_packets_post, in_packets_pre + kPacketCount);
321+
// Counter should increment by no more than 110% of packets than were sent.
322+
// This allows for tolerance of non-test packets such as router solicitation
323+
// packets and others.
324+
EXPECT_LE(in_packets_post, in_packets_pre + (kPacketCount * 1.1));
325+
LOG(INFO) << absl::StrCat("in_packets_post: ", in_packets_post,
326+
", in_packets_pre: ", in_packets_pre);
327+
328+
// SUT egress port gnmi name is hard coded for now, based on the egress port
329+
// oid that's chosen by the manual component test to set up the mirror
330+
// session on the SUT. This will be replaced by the egress port that is
331+
// used when creating that mirror session via P4RT.
332+
ASSERT_OK_AND_ASSIGN(
333+
uint64_t out_packets_post,
334+
pctutil::GetGnmiStat("out-unicast-pkts",
335+
link_used_for_test_packets.sut_mtp_port_gnmi_name,
336+
sut_gnmi_stub.get()));
337+
EXPECT_GE(out_packets_post, out_packets_pre + kPacketCount);
338+
EXPECT_LE(out_packets_post, out_packets_pre + (kPacketCount * 1.1));
339+
LOG(INFO) << absl::StrCat("out_packets_post: ", out_packets_post,
340+
", out_packets_pre: ", out_packets_pre);
341+
}
342+
343+
} // anonymous namespace
344+
} // namespace pins_test

0 commit comments

Comments
 (0)