From 3c816b5c50fef078aa48a5a36357c1cfcede33bf Mon Sep 17 00:00:00 2001 From: Atabak Nojavan Date: Wed, 5 Mar 2025 16:57:03 -0500 Subject: [PATCH] Adding pcap_mock test; finding and fixing bugs using it. Added a libpcap mock for testing the BMI code in P4 behavioral-model by injecting packets using the actual BMI code instead of their nanomsg based injector (which is bypassing the BMI code) It is not meant to be a full mock of libpcap, it only implements the functions required for this special purpose. Bugs fixed bmi_port.c: In bmi_port_destroy_mgr function, remove interfaces in reverse order. The previous code removed only half of the interfaces because it was iterating through an array while removing its members. In _bmi_port_interface_remove function, bug fix to prevent a deadlock. Function uses "write" to write on a pipe and waits for the answer using "read" while it holds the lock "port_mgr->lock". Meanwhile, the "run_select" thread requires to get that lock once per iteration of the loop, causing a deadlock. dev_mgr_bmi.cpp: In BmiDevMgrImp class, p_monitor should be stopped in destructor and before destruction of port_mgr to prevent pure virtual function call. --- configure.ac | 1 + src/BMI/bmi_port.c | 4 +- src/bm_sim/dev_mgr_bmi.cpp | 1 + targets/simple_switch/tests/Makefile.am | 12 + .../simple_switch/tests/pcap_mock/Makefile.am | 10 + .../tests/pcap_mock/mock_libpcap.cpp | 299 ++++++ .../tests/pcap_mock/pcap_mock.hpp | 49 + .../tests/test_packet_redirect_bmi.cpp | 893 ++++++++++++++++++ 8 files changed, 1268 insertions(+), 1 deletion(-) create mode 100644 targets/simple_switch/tests/pcap_mock/Makefile.am create mode 100644 targets/simple_switch/tests/pcap_mock/mock_libpcap.cpp create mode 100644 targets/simple_switch/tests/pcap_mock/pcap_mock.hpp create mode 100644 targets/simple_switch/tests/test_packet_redirect_bmi.cpp diff --git a/configure.ac b/configure.ac index cd3ecaf29..a0d42902d 100755 --- a/configure.ac +++ b/configure.ac @@ -324,6 +324,7 @@ AC_CONFIG_FILES([Makefile targets/l2_switch/learn_client/Makefile targets/simple_switch/Makefile targets/simple_switch/tests/Makefile + targets/simple_switch/tests/pcap_mock/Makefile targets/simple_switch/tests/CLI_tests/Makefile targets/psa_switch/Makefile targets/psa_switch/tests/Makefile diff --git a/src/BMI/bmi_port.c b/src/BMI/bmi_port.c index 7ad82dba1..2ace31672 100644 --- a/src/BMI/bmi_port.c +++ b/src/BMI/bmi_port.c @@ -319,7 +319,9 @@ static int _bmi_port_interface_remove(bmi_port_mgr_t *port_mgr, int port_num) { char buf[1] = {'\x00'}; write(port_mgr->socketpairfd[0], buf, sizeof(buf)); + pthread_rwlock_unlock(&port_mgr->lock); read(port_mgr->socketpairfd[0], buf, sizeof(buf)); + pthread_rwlock_wrlock(&port_mgr->lock); if (bmi_interface_destroy(port->bmi) != 0) return -1; @@ -343,7 +345,7 @@ int bmi_port_destroy_mgr(bmi_port_mgr_t *port_mgr) { pthread_rwlock_wrlock(&port_mgr->lock); bmi_port_t *port; int i; - for (i = 0; i < port_mgr->port_count; i++) { + for (i = port_mgr->port_count - 1; i >= 0; i--) { port = &port_mgr->ports[i]; _bmi_port_interface_remove(port_mgr, port->port_num); } diff --git a/src/bm_sim/dev_mgr_bmi.cpp b/src/bm_sim/dev_mgr_bmi.cpp index 00d84764b..4c04f623f 100644 --- a/src/bm_sim/dev_mgr_bmi.cpp +++ b/src/bm_sim/dev_mgr_bmi.cpp @@ -57,6 +57,7 @@ class BmiDevMgrImp : public DevMgrIface { private: ~BmiDevMgrImp() override { + p_monitor->stop(); bmi_port_destroy_mgr(port_mgr); } diff --git a/targets/simple_switch/tests/Makefile.am b/targets/simple_switch/tests/Makefile.am index 803b1e2f7..55dc43c0c 100644 --- a/targets/simple_switch/tests/Makefile.am +++ b/targets/simple_switch/tests/Makefile.am @@ -20,6 +20,7 @@ $(top_builddir)/src/bm_apps/libbmapps.la \ # Define unit tests common_source = main.cpp ../../test_utils/utils.cpp ../../test_utils/utils.h TESTS = test_packet_redirect \ +test_packet_redirect_bmi \ test_truncate \ test_swap \ test_queueing \ @@ -27,23 +28,34 @@ test_recirc \ test_parser_error check_PROGRAMS = $(TESTS) test_all +lib_LIBRARIES = pcap_mock/libpcap.a # Sources for tests test_packet_redirect_SOURCES = $(common_source) test_packet_redirect.cpp +test_packet_redirect_bmi_SOURCES = $(common_source) test_packet_redirect_bmi.cpp +test_packet_redirect_bmi_LDFLAGS = -L$(srcdir)/pcap_mock +test_packet_redirect_bmi_DEPENDENCIES = pcap_mock/libpcap.a test_truncate_SOURCES = $(common_source) test_truncate.cpp test_swap_SOURCES = $(common_source) test_swap.cpp test_queueing_SOURCES = $(common_source) test_queueing.cpp test_recirc_SOURCES = $(common_source) test_recirc.cpp test_parser_error_SOURCES = $(common_source) test_parser_error.cpp +pcap_mock_libpcap_a_SOURCES = pcap_mock/mock_libpcap.cpp pcap_mock/pcap_mock.hpp +pcap_mock_libpcap_a_LIBADD = + test_all_SOURCES = $(common_source) \ test_packet_redirect.cpp \ +test_packet_redirect_bmi.cpp \ test_truncate.cpp \ test_swap.cpp \ test_queueing.cpp \ test_recirc.cpp \ test_parser_error.cpp +test_all_LDFLAGS = -L$(srcdir)/pcap_mock +test_all_DEPENDENCIES = pcap_mock/libpcap.a + EXTRA_DIST = \ testdata/packet_redirect.json \ testdata/truncate.json \ diff --git a/targets/simple_switch/tests/pcap_mock/Makefile.am b/targets/simple_switch/tests/pcap_mock/Makefile.am new file mode 100644 index 000000000..19c874899 --- /dev/null +++ b/targets/simple_switch/tests/pcap_mock/Makefile.am @@ -0,0 +1,10 @@ +AM_CXXFLAGS = -g -pthread -std=c++20 +AM_LDFLAGS = -L. +LDLIBS = -lpcap + +bin_PROGRAMS = test_pcap +lib_LIBRARIES = libpcap.a + +libpcap_a_SOURCES = mock_libpcap.cpp pcap_mock.hpp + +libpcap_a_LIBADD = diff --git a/targets/simple_switch/tests/pcap_mock/mock_libpcap.cpp b/targets/simple_switch/tests/pcap_mock/mock_libpcap.cpp new file mode 100644 index 000000000..f46c01d49 --- /dev/null +++ b/targets/simple_switch/tests/pcap_mock/mock_libpcap.cpp @@ -0,0 +1,299 @@ +/* * Copyright 2025. + * + * 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. + */ + +/* + * Francois-R.Boyer@PolyMtl.ca + * + */ + +/* A libpcap mock for testing the BMI code in P4 behavioral-model. +It is not meant to be a full mock of libpcap, it only implements the functions +required for this special purpose. +Can be compiled as a static library libpcap.a and be linked instead of +standard libpcap.a. +By Francois-R.Boyer@PolyMtl.ca +2024-07 +*/ + +#include "pcap_mock.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SHOULD_TRACE 1 +#if SHOULD_TRACE +#define IF_TRACE(X) X +#else +#define IF_TRACE(X) +#endif + +namespace { +std::mutex pcap_objects_by_name_mutex; +std::map pcap_objects_by_name; +} + +struct pcap { +#define PCAP_ERROR_IF_ACTIVATED if (is_activated_) return PCAP_ERROR_ACTIVATED; + using Packet = pcap_mock::Packet; + struct MutexedPacketList { + std::mutex list_mutex; + std::list packets; + }; + using FileDescriptor = int; + static constexpr FileDescriptor invalid_file_descriptor = -1; + static constexpr int no_error = 0; + pcap(const char* source, char* errbuf) : + name_(source), + file_descriptor_(eventfd(0, 0)) + //NOTE: Could use EFD_SEMAPHORE and decrement count each + //time we give a packet. + { + IF_TRACE(printf("pcap_open(%s) %p\n", source, this);); + add_pcap(this, source); + } + + ~pcap() { + IF_TRACE(printf("pcap_close(%p %s)\n", this, name_.c_str());); + clear(); + close(file_descriptor_); + remove_pcap(this); + } + + int set_promisc(int promisc) { + PCAP_ERROR_IF_ACTIVATED; + is_promisc_ = promisc != 0; + return no_error; + } + int set_timeout(int to_ms) { + PCAP_ERROR_IF_ACTIVATED timeout_ms_ = to_ms; + return no_error; + } + int set_immediate_mode(int immediate_mode) { + PCAP_ERROR_IF_ACTIVATED; + is_immediate_mode_ = immediate_mode != 0; + return no_error; + } + + int activate() { + PCAP_ERROR_IF_ACTIVATED; + is_activated_ = true; + return no_error; + } + + int sendpacket(const u_char* buf, int size) { + IF_TRACE(printf("%p %s->sendpacket(,%d)\n", this, name_.c_str(), size);); + std::lock_guard lock(packets_sent_.list_mutex); + timeval t; gettimeofday(&t, nullptr); + packets_sent_.packets.push_back({t, std::vector(buf, buf+size), + this}); + return no_error; + } + FileDescriptor get_selectable_fd() { return file_descriptor_; } + + int next_ex(struct pcap_pkthdr** pkt_header, const u_char** pkt_data) { + std::lock_guard lock(packets_receive_buffer_.list_mutex); + if (packets_receive_buffer_.packets.empty()) + return 0; + + current_receive_packet_ = std::move( + packets_receive_buffer_.packets.front()); + packets_receive_buffer_.packets.pop_front(); + if (packets_receive_buffer_.packets.empty()) + read_eventfd(); + + current_receive_packet_header_.ts = current_receive_packet_.time_stamp; + current_receive_packet_header_.caplen = current_receive_packet_header_.len = + current_receive_packet_.data.size(); + *pkt_header = ¤t_receive_packet_header_; + *pkt_data = current_receive_packet_.data.data(); + return 1; + } + + std::string get_name() const; + + bool is_promisc() const; + bool is_immediate_mode() const; + + void simulate_packets_received(std::vector> packets_data); + static pcap* get_pcap_object(const std::string& name); + static Packet get_sent_packet(int timeout_ms = -1); + void clear(); + +#undef PCAP_ERROR_IF_ACTIVATED +private: + uint64_t read_eventfd() { + uint64_t val; + assert(read(file_descriptor_, &val, sizeof(val)) == sizeof(val)); + return val; + } + void write_eventfd(uint64_t to_add = 1) { + assert(write(file_descriptor_, &to_add, sizeof(to_add)) == sizeof(to_add)); + } + + static void add_pcap(pcap* object, const std::string& name) { + std::lock_guard lock(pcap_objects_by_name_mutex); + assert(pcap_objects_by_name.count(name) == 0); + pcap_objects_by_name[name] = object; + } + static void remove_pcap(pcap* object) { + std::lock_guard lock(pcap_objects_by_name_mutex); + pcap_objects_by_name.erase(object->name_); + } + + std::string name_; + + bool is_activated_ = false; + bool is_promisc_ = false; + int timeout_ms_ = -1; + bool is_immediate_mode_ = false; + + FileDescriptor file_descriptor_ = invalid_file_descriptor; + + static MutexedPacketList packets_sent_; + MutexedPacketList packets_receive_buffer_; + Packet current_receive_packet_; + pcap_pkthdr current_receive_packet_header_; +}; + +pcap::MutexedPacketList pcap::packets_sent_; + +std::string pcap::get_name() const { return name_; } + +bool pcap::is_promisc() const { return is_promisc_; } +bool pcap::is_immediate_mode() const { + return timeout_ms_ == 0 && is_immediate_mode_; +} + +void pcap::simulate_packets_received( + std::vector> packets_data) { + IF_TRACE(printf("%p %s->simulate_packets_received([%d])\n", + this, name_.c_str(), packets_data.size());); + std::lock_guard lock(packets_receive_buffer_.list_mutex); + timeval t; gettimeofday(&t, nullptr); + for (auto&& p : packets_data) + packets_receive_buffer_.packets.push_back({t, p}); + write_eventfd(packets_data.size()); +} +pcap* pcap::get_pcap_object(const std::string& name) { + std::lock_guard lock(pcap_objects_by_name_mutex); + auto it = pcap_objects_by_name.find(name); + return (it != pcap_objects_by_name.end()) ? it->second : nullptr; +} +pcap::Packet pcap::get_sent_packet(int timeout_ms) { + //TODO: timeout_ms + //TODO: wait intelligently + while (true) { + if (std::lock_guard lock(packets_sent_.list_mutex); + !packets_sent_.packets.empty()) { + Packet result = packets_sent_.packets.front(); + packets_sent_.packets.pop_front(); + IF_TRACE(printf("get_sent_packet got %s %d \n", + result.pcap_object->get_name().c_str(), result.data.size());); + return result; + } + using namespace std::literals::chrono_literals; + std::this_thread::sleep_for(1ms); + } +} +void pcap::clear() { + IF_TRACE(printf("%p %s->clear()\n", this, name_.c_str());); + { + std::lock_guard lock(packets_receive_buffer_.list_mutex); + packets_receive_buffer_.packets.clear(); + } + { + std::lock_guard lock(packets_sent_.list_mutex); + packets_sent_.packets.clear(); + } +} + +namespace pcap_mock { + std::string get_name(const pcap* p) { return p->get_name(); } + bool is_promisc(const pcap* p) { return p->is_promisc(); } + bool is_immediate_mode(const pcap* p) { return p->is_immediate_mode(); } + void simulate_packets_received(pcap* p, + std::vector> packets_data) { + p->simulate_packets_received(move(packets_data)); + } + + pcap* get_pcap_object(const std::string& name) { + return pcap::get_pcap_object(name); + } + Packet get_sent_packet(int timeout_ms) { + return pcap::get_sent_packet(timeout_ms); + } + void clear(pcap* p) { p->clear(); } +} + +extern "C" { +pcap_t* pcap_create(const char* source, char* errbuf) { + return new pcap(source, errbuf); +} + +int pcap_set_promisc(pcap_t* p, int promisc) { + return p->set_promisc(promisc); +} + +int pcap_set_timeout(pcap_t* p, int to_ms) { + return p->set_timeout(to_ms); +} + +int pcap_set_immediate_mode(pcap_t* p, int immediate_mode) { + return p->set_immediate_mode(immediate_mode); +} + +int pcap_activate(pcap_t* p) { return p->activate(); } + +void pcap_close(pcap_t* p) { delete p; } + +int pcap_sendpacket(pcap_t* p, const u_char* buf, int size) { + return p->sendpacket(buf, size); +} +int pcap_get_selectable_fd(pcap_t* p) { return p->get_selectable_fd(); } +int pcap_next_ex(pcap_t* p, struct pcap_pkthdr** pkt_header, + const u_char** pkt_data) { + return p->next_ex(pkt_header, pkt_data); + } + +// Not actually used by behavioral-model. +const char* pcap_statustostr(int error) { + std::ostringstream os; os << "Unknown error: " << error; + static std::string errorString; errorString = os.str(); + return errorString.c_str(); +} + +// "Empty" stubs, so P4 behavioral-model compiles, but should not be used +// for testing. +struct pcap_dumper {}; +pcap_dumper_t* pcap_dump_open(pcap_t* p, const char *fname) { + static pcap_dumper nothing; return ¬hing; +} +void pcap_dump_close(pcap_dumper_t* p) {} +void pcap_dump(u_char* user, const struct pcap_pkthdr* h, const u_char* sp) {} +int pcap_dump_flush(pcap_dumper_t* p) { return 0; } + +pcap_t* pcap_open_dead(int linktype, int snaplen) { return nullptr; } +pcap_t* pcap_open_offline(const char* fname, char* errbuf) { return nullptr; } +} +// vi: ts=4 diff --git a/targets/simple_switch/tests/pcap_mock/pcap_mock.hpp b/targets/simple_switch/tests/pcap_mock/pcap_mock.hpp new file mode 100644 index 000000000..4f8f9730e --- /dev/null +++ b/targets/simple_switch/tests/pcap_mock/pcap_mock.hpp @@ -0,0 +1,49 @@ +/* * Copyright 2025. + * + * 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. + */ + +/* + * Francois-R.Boyer@PolyMtl.ca + * + */ + +/* A libpcap mock for testing the BMI code in P4 behavioral-model. +It is not meant to be a full mock of libpcap, it only implements the functions +required for this special purpose. +Can be compiled as a static library libpcap.a and be linked instead of +standard libpcap.a. +By Francois-R.Boyer@PolyMtl.ca +2024-07 +*/ +#include +#include +#include + +struct pcap; +namespace pcap_mock { + struct Packet { + timeval time_stamp; + std::vector data; + pcap* pcap_object; + }; + std::string get_name(const pcap*); + bool is_promisc(const pcap*); + bool is_immediate_mode(const pcap*); + void simulate_packets_received(pcap*, + std::vector>packets_data); + + pcap* get_pcap_object(const std::string& name); + Packet get_sent_packet(int timeout_ms = -1); + void clear(pcap*); +} diff --git a/targets/simple_switch/tests/test_packet_redirect_bmi.cpp b/targets/simple_switch/tests/test_packet_redirect_bmi.cpp new file mode 100644 index 000000000..903f0ff9c --- /dev/null +++ b/targets/simple_switch/tests/test_packet_redirect_bmi.cpp @@ -0,0 +1,893 @@ +/* * Copyright 2025. + * + * 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. + */ + +/* + * Francois-R.Boyer@PolyMtl.ca + * Based on code of "test_packet_redirect.cpp" + * By Antonin Bas + * + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "simple_switch.h" + +#include "utils.h" +#include "pcap_mock/pcap_mock.hpp" + +namespace fs = boost::filesystem; + +using bm::MatchErrorCode; +using bm::ActionData; +using bm::MatchKeyParam; +using bm::entry_handle_t; + + +namespace { + +class Ports { +public: + static std::string generate_name_from_number(int port_number) { + return "i" + std::to_string(port_number); + } + static int get_number_from_name(const std::string port_name) { + assert(port_name[0] == 'i'); + return atoi(&port_name[1]); + } + //NOTE: Port numbers are 0-based, so number can be directly used as an index. + static void add_args_for_n_ports(std::vector& args_to_modify, + int n_ports) { + for (int i = 0; i < n_ports; ++i) { + args_to_modify.push_back("-i"); + args_to_modify.push_back( + std::to_string(i)+"@"+generate_name_from_number(i)); + } + } + void update_ports(int n_ports) { + ports_.clear(); + for (int i = 0; i < n_ports; ++i) + ports_.push_back( + pcap_mock::get_pcap_object(generate_name_from_number(i))); + } + pcap* get_port_from_number(int port_number) { + return ports_.at(port_number); + } + int get_number_from_port(pcap* port) { + return get_number_from_name(pcap_mock::get_name(port)); + } + + void send(int port, const char* data, size_t data_size) { + pcap_mock::simulate_packets_received(get_port_from_number(port), + std::vector>{ + std::vector(data, + data+data_size) + }); + } + void read(char* buffer, size_t length, int* received_port_out) { + pcap_mock::Packet p = pcap_mock::get_sent_packet(); + memcpy(buffer, p.data.data(), std::min(p.data.size(), length)); + *received_port_out = get_number_from_port(p.pcap_object); + } + void start() { + for (auto&& p : ports_) + if (p) pcap_mock::clear(p); + } + +private: + std::vector ports_; +}; + + +} // namespace + +class SimpleSwitch_PacketRedirectP4_BMI : public ::testing::Test { + protected: + static constexpr size_t kMaxBufSize = 512; + + static constexpr bm::device_id_t device_id{0}; + + SimpleSwitch_PacketRedirectP4_BMI() + : //packet_inject(packet_in_addr), + // packet_inject(interfaces), + events(event_logger_addr) { } + + // Per-test-case set-up. + // We make the switch a shared resource for all tests. This is mainly because + // the simple_switch target detaches threads + static void SetUpTestCase() { + // bm::Logger::set_logger_console(); +#ifdef BM_ELOG_ON + auto event_transport = bm::TransportIface::make_nanomsg(event_logger_addr); + event_transport->open(); + bm::EventLogger::init(std::move(event_transport)); +#endif + + test_switch = new SimpleSwitch(8); // 8 ports + + // load JSON + fs::path json_path = fs::path(testdata_dir) / fs::path(test_json); + test_switch->init_objects(json_path.string()); + + std::vector args = { "test_packet_redirect_bmi", "--no-p4" }; + static constexpr int n_interfaces = 5; + Ports::add_args_for_n_ports(args, n_interfaces); + std::vector argv; for (auto&& a : args) argv.push_back(a.data()); + test_switch->init_from_command_line_options(argv.size(), argv.data(), + nullptr, nullptr); + packet_inject.update_ports(n_interfaces); + + test_switch->start_and_return(); + } + + // Per-test-case tear-down. + static void TearDownTestCase() { + delete test_switch; + bm::EventLogger::init(nullptr); // close nanomsg socket + } + + void SetUp() override { + packet_inject.start(); + + events.start(); + + // default actions for all tables + test_switch->mt_set_default_action(0, "t_ingress_1", "_nop", ActionData()); + test_switch->mt_set_default_action(0, "t_ingress_2", "_nop", ActionData()); + test_switch->mt_set_default_action(0, "t_egress", "_nop", ActionData()); + test_switch->mt_set_default_action(0, "t_exit", "set_hdr", ActionData()); + } + + void TearDown() override { + // kind of experimental, so reserved for testing + test_switch->reset_state(); + } + + bool check_event_table_hit(const NNEventListener::NNEvent &event, + const std::string &name) { + return (event.type == NNEventListener::TABLE_HIT) && + (event.id == test_switch->get_table_id(name)); + } + + bool check_event_table_miss(const NNEventListener::NNEvent &event, + const std::string &name) { + return (event.type == NNEventListener::TABLE_MISS) && + (event.id == test_switch->get_table_id(name)); + } + + bool check_event_action_execute(const NNEventListener::NNEvent &event, + const std::string &t_name, + const std::string &a_name) { + return (event.type == NNEventListener::ACTION_EXECUTE) && + (event.id == test_switch->get_action_id(t_name, a_name)); + } + + static std::string pid(packet_id_t copy_id) { + return std::to_string(SimpleSwitch::get_packet_id()) + "." + + std::to_string(copy_id); + } + + protected: + static const char event_logger_addr[]; + static const char packet_in_addr[]; + static SimpleSwitch *test_switch; + // static Ports ports; + // bm_apps::PacketInject packet_inject; + static Ports packet_inject; + // PacketInReceiver receiver{}; + static constexpr Ports& receiver = packet_inject; + NNEventListener events; + + private: + static const char testdata_dir[]; + static const char test_json[]; +}; + +// In theory, I could be using an 'inproc' transport here. However, I observe a +// high number of packet drops when switching to 'inproc', which is obviosuly +// causing the tests to fail. PUB/SUB is not a reliable protocol and therefore +// packet drops are to be expected when the phblisher is faster than the +// consummer. However, I do not believe my consummer is that slow and I never +// observe the drops with 'ipc' +const char SimpleSwitch_PacketRedirectP4_BMI::event_logger_addr[] = + "ipc:///tmp/test_events_abc123"; +const char SimpleSwitch_PacketRedirectP4_BMI::packet_in_addr[] = + "ipc:///tmp/test_packet_in_abc123"; + +SimpleSwitch *SimpleSwitch_PacketRedirectP4_BMI::test_switch = nullptr; +Ports SimpleSwitch_PacketRedirectP4_BMI::packet_inject; + +const char SimpleSwitch_PacketRedirectP4_BMI::testdata_dir[] = TESTDATADIR; +const char SimpleSwitch_PacketRedirectP4_BMI::test_json[] = + "packet_redirect.json"; + +TEST_F(SimpleSwitch_PacketRedirectP4_BMI, Baseline) { + static constexpr int port_in = 1; + static constexpr int port_out = 2; + + std::vector match_key; + match_key.emplace_back(MatchKeyParam::Type::EXACT, std::string("\x01")); + match_key.emplace_back(MatchKeyParam::Type::EXACT, std::string("\x00", 1)); + ActionData data; + data.push_back_action_data(port_out); + entry_handle_t handle; + MatchErrorCode rc = test_switch->mt_add_entry(0, "t_ingress_1", match_key, + "_set_port", std::move(data), + &handle); + ASSERT_EQ(MatchErrorCode::SUCCESS, rc); + const char pkt[] = {'\x01', '\x00', '\x00', '\x00'}; + packet_inject.send(port_in, pkt, sizeof(pkt)); + + // int recv_port = port_out; + char recv_buffer[kMaxBufSize]; + int recv_port = -1; + receiver.read(recv_buffer, sizeof(pkt), &recv_port); + // make sure that standard_metadata.packet_length was updated after removing + // header + ASSERT_EQ(2, static_cast(recv_buffer[1])); + ASSERT_EQ(port_out, recv_port); + +#ifdef BM_ELOG_ON + // event check + std::vector pevents; + events.get_and_remove_events(pid(0), &pevents, 8u); + ASSERT_EQ(8u, pevents.size()); + ASSERT_TRUE(check_event_table_hit(pevents[0], "t_ingress_1")); + ASSERT_TRUE(check_event_action_execute(pevents[1], "t_ingress_1", + "_set_port")); + ASSERT_TRUE(check_event_table_miss(pevents[2], "t_ingress_2")); + ASSERT_TRUE(check_event_action_execute(pevents[3], "t_ingress_2", "_nop")); + ASSERT_TRUE(check_event_table_miss(pevents[4], "t_egress")); + ASSERT_TRUE(check_event_action_execute(pevents[5], "t_egress", "_nop")); + ASSERT_TRUE(check_event_table_miss(pevents[6], "t_exit")); + ASSERT_TRUE(check_event_action_execute(pevents[7], "t_exit", "set_hdr")); +#endif +} + +namespace { + +struct PreTestConfiguration { + PreTestConfiguration(McSimplePreLAG *pre, int mgrp, + std::initializer_list ports) + : pre(pre) { + EXPECT_EQ(McSimplePreLAG::SUCCESS, pre->mc_mgrp_create(mgrp, &mgrp_hdl)); + for (auto port : ports) { + McSimplePreLAG::PortMap port_map; + port_map[port] = true; + McSimplePreLAG::LagMap lag_map; + node_hdls.emplace_back(); + auto &node_hdl = node_hdls.back(); + EXPECT_EQ(McSimplePreLAG::SUCCESS, + pre->mc_node_create(port, port_map, lag_map, &node_hdl)); + EXPECT_EQ(McSimplePreLAG::SUCCESS, + pre->mc_node_associate(mgrp_hdl, node_hdl)); + } + } + + ~PreTestConfiguration() { + for (auto node_hdl : node_hdls) + EXPECT_EQ(McSimplePreLAG::SUCCESS, pre->mc_node_destroy(node_hdl)); + EXPECT_EQ(McSimplePreLAG::SUCCESS, pre->mc_mgrp_destroy(mgrp_hdl)); + } + + McSimplePreLAG *pre; // non-owning pointer + McSimplePreLAG::mgrp_hdl_t mgrp_hdl; + std::vector node_hdls; +}; + +} // namespace + +TEST_F(SimpleSwitch_PacketRedirectP4_BMI, Multicast) { + static constexpr int port_in = 1; + static constexpr int mgrp = 1; + + std::vector match_key; + match_key.emplace_back(MatchKeyParam::Type::EXACT, std::string("\x02")); + match_key.emplace_back(MatchKeyParam::Type::EXACT, std::string("\x00", 1)); + ActionData data; + data.push_back_action_data(mgrp); + entry_handle_t handle; + MatchErrorCode rc = test_switch->mt_add_entry(0, "t_ingress_1", match_key, + "_multicast", std::move(data), + &handle); + ASSERT_EQ(MatchErrorCode::SUCCESS, rc); + + auto pre = test_switch->get_component(); + SCOPED_TRACE("SimpleSwitch_PacketRedirectP4_BMI.Multicast"); + PreTestConfiguration pre_config(pre.get(), mgrp, {1, 2}); + + const char pkt[] = {'\x02', '\x00', '\x00', '\x00'}; + packet_inject.send(port_in, pkt, sizeof(pkt)); + char recv_buffer[kMaxBufSize]; + int recv_port_1 = -1; + int recv_port_2 = -1; + receiver.read(recv_buffer, sizeof(pkt), &recv_port_1); + // make sure that standard_metadata.packet_length was updated for copies + ASSERT_EQ(2, static_cast(recv_buffer[1])); + receiver.read(recv_buffer, sizeof(pkt), &recv_port_2); + ASSERT_EQ(2, static_cast(recv_buffer[1])); + ASSERT_TRUE((recv_port_1 == 1 && recv_port_2 == 2) || + (recv_port_1 == 2 && recv_port_2 == 1)); + +#ifdef BM_ELOG_ON + // event check + std::vector pevents; + + events.get_and_remove_events(pid(0), &pevents, 4u); + ASSERT_EQ(4u, pevents.size()); + ASSERT_TRUE(check_event_table_hit(pevents[0], "t_ingress_1")); + ASSERT_TRUE(check_event_action_execute(pevents[1], "t_ingress_1", + "_multicast")); + ASSERT_TRUE(check_event_table_miss(pevents[2], "t_ingress_2")); + ASSERT_TRUE(check_event_action_execute(pevents[3], "t_ingress_2", "_nop")); + + events.get_and_remove_events(pid(1), &pevents, 4u); + ASSERT_EQ(4u, pevents.size()); + ASSERT_TRUE(check_event_table_miss(pevents[0], "t_egress")); + ASSERT_TRUE(check_event_action_execute(pevents[1], "t_egress", "_nop")); + ASSERT_TRUE(check_event_table_miss(pevents[2], "t_exit")); + ASSERT_TRUE(check_event_action_execute(pevents[3], "t_exit", "set_hdr")); + + events.get_and_remove_events(pid(2), &pevents, 4u); + ASSERT_EQ(4u, pevents.size()); + ASSERT_TRUE(check_event_table_miss(pevents[0], "t_egress")); + ASSERT_TRUE(check_event_action_execute(pevents[1], "t_egress", "_nop")); + ASSERT_TRUE(check_event_table_miss(pevents[2], "t_exit")); + ASSERT_TRUE(check_event_action_execute(pevents[3], "t_exit", "set_hdr")); +#endif +} + +class SimpleSwitch_PacketRedirectP4_CloneI2E_BMI + : public SimpleSwitch_PacketRedirectP4_BMI { + protected: + void add_entries(int port_out, int mirror_id) { + std::vector match_key_1; + match_key_1.emplace_back(MatchKeyParam::Type::EXACT, std::string("\x03")); + match_key_1.emplace_back(MatchKeyParam::Type::EXACT, + std::string("\x00", 1)); + ActionData data_1; + data_1.push_back_action_data(port_out); + entry_handle_t h_1; + EXPECT_EQ(MatchErrorCode::SUCCESS, + test_switch->mt_add_entry(0, "t_ingress_1", match_key_1, + "_set_port", std::move(data_1), &h_1)); + + std::vector match_key_2; + match_key_2.emplace_back(MatchKeyParam::Type::EXACT, std::string("\x03")); + match_key_2.emplace_back(MatchKeyParam::Type::TERNARY, + std::string(4, '\x00'), std::string(4, '\x00')); + ActionData data_2; + data_2.push_back_action_data(mirror_id); + entry_handle_t h_2; + EXPECT_EQ(MatchErrorCode::SUCCESS, + test_switch->mt_add_entry(0, "t_ingress_2", match_key_2, + "_clone_i2e", std::move(data_2), + &h_2, 1)); + } +}; + +TEST_F(SimpleSwitch_PacketRedirectP4_CloneI2E_BMI, CloneI2E) { + static constexpr int port_in = 1; + static constexpr int port_out = 2; + static constexpr int port_out_copy = 3; + static constexpr int mirror_id = 1; + + add_entries(port_out, mirror_id); + + SimpleSwitch::MirroringSessionConfig config = {}; + config.egress_port = port_out_copy; + config.egress_port_valid = true; + test_switch->mirroring_add_session(mirror_id, config); + + const char pkt[] = {'\x03', '\x00', '\x00', '\x00'}; + packet_inject.send(port_in, pkt, sizeof(pkt)); + char recv_buffer[kMaxBufSize]; + int recv_port_1 = -1; + int recv_port_2 = -1; + receiver.read(recv_buffer, sizeof(pkt), &recv_port_1); + // check standard_metadata.packet_length: it should be 2 for the original + // packet and 4 for the clone + if (recv_port_1 == port_out) { + ASSERT_EQ(2, static_cast(recv_buffer[1])); + } else { + ASSERT_EQ(4, static_cast(recv_buffer[1])); + } + receiver.read(recv_buffer, sizeof(pkt), &recv_port_2); + if (recv_port_2 == port_out) { + ASSERT_EQ(2, static_cast(recv_buffer[1])); + } else { + ASSERT_EQ(4, static_cast(recv_buffer[1])); + } + // TODO(antonin): make sure the right packet comes out of the right port + ASSERT_TRUE((recv_port_1 == port_out && recv_port_2 == port_out_copy) || + (recv_port_1 == port_out_copy && recv_port_2 == port_out)); + + test_switch->mirroring_delete_session(mirror_id); + +#ifdef BM_ELOG_ON + // event check + std::vector pevents; + + events.get_and_remove_events(pid(0), &pevents, 8u); + ASSERT_EQ(8u, pevents.size()); + ASSERT_TRUE(check_event_table_hit(pevents[0], "t_ingress_1")); + ASSERT_TRUE(check_event_action_execute(pevents[1], "t_ingress_1", + "_set_port")); + ASSERT_TRUE(check_event_table_hit(pevents[2], "t_ingress_2")); + ASSERT_TRUE(check_event_action_execute(pevents[3], "t_ingress_2", + "_clone_i2e")); + ASSERT_TRUE(check_event_table_miss(pevents[4], "t_egress")); + ASSERT_TRUE(check_event_action_execute(pevents[5], "t_egress", "_nop")); + ASSERT_TRUE(check_event_table_miss(pevents[6], "t_exit")); + ASSERT_TRUE(check_event_action_execute(pevents[7], "t_exit", "set_hdr")); + + events.get_and_remove_events(pid(1), &pevents, 4u); + ASSERT_EQ(4u, pevents.size()); + ASSERT_TRUE(check_event_table_miss(pevents[0], "t_egress")); + ASSERT_TRUE(check_event_action_execute(pevents[1], "t_egress", "_nop")); + ASSERT_TRUE(check_event_table_miss(pevents[2], "t_exit")); + ASSERT_TRUE(check_event_action_execute(pevents[3], "t_exit", "set_hdr")); +#endif +} + +TEST_F(SimpleSwitch_PacketRedirectP4_CloneI2E_BMI, CloneI2E_Multicast) { + static constexpr int port_in = 1; + static constexpr int port_out = 2; + static constexpr int port_out_copy = 3; + static constexpr int port_out_mc_copy = 4; + static constexpr int mirror_id = 1; + static constexpr int mgrp = 1; + + add_entries(port_out, mirror_id); + + SimpleSwitch::MirroringSessionConfig config = {}; + config.egress_port = port_out_copy; + config.egress_port_valid = true; + config.mgid = mgrp; + config.mgid_valid = true; + test_switch->mirroring_add_session(mirror_id, config); + + auto pre = test_switch->get_component(); + SCOPED_TRACE("SimpleSwitch_PacketRedirectP4_CloneI2E_BMI.CloneI2E_Multicast"); + PreTestConfiguration pre_config(pre.get(), mgrp, {port_out_mc_copy}); + + const char pkt[] = {'\x03', '\x00', '\x00', '\x00'}; + packet_inject.send(port_in, pkt, sizeof(pkt)); + char recv_buffer[kMaxBufSize]; + int recv_port_1 = -1, recv_port_2 = -1, recv_port_3 = -1; + receiver.read(recv_buffer, sizeof(pkt), &recv_port_1); + // check standard_metadata.packet_length: it should be 2 for the original + // packet and 4 for the clone and multicast copies + if (recv_port_1 == port_out) { + ASSERT_EQ(2, static_cast(recv_buffer[1])); + } else { + ASSERT_EQ(4, static_cast(recv_buffer[1])); + } + receiver.read(recv_buffer, sizeof(pkt), &recv_port_2); + if (recv_port_2 == port_out) { + ASSERT_EQ(2, static_cast(recv_buffer[1])); + } else { + ASSERT_EQ(4, static_cast(recv_buffer[1])); + } + receiver.read(recv_buffer, sizeof(pkt), &recv_port_3); + if (recv_port_3 == port_out) { + ASSERT_EQ(2, static_cast(recv_buffer[1])); + } else { + ASSERT_EQ(4, static_cast(recv_buffer[1])); + } + // TODO(antonin): make sure the right packet comes out of the right port + ASSERT_EQ(std::set({recv_port_1, recv_port_2, recv_port_3}), + std::set({port_out, port_out_copy, port_out_mc_copy})); + + test_switch->mirroring_delete_session(mirror_id); + return; + +#ifdef BM_ELOG_ON + // event check + std::vector pevents; + + events.get_and_remove_events(pid(0), &pevents, 8u); + ASSERT_EQ(8u, pevents.size()); + ASSERT_TRUE(check_event_table_hit(pevents[0], "t_ingress_1")); + ASSERT_TRUE(check_event_action_execute(pevents[1], "t_ingress_1", + "_set_port")); + ASSERT_TRUE(check_event_table_hit(pevents[2], "t_ingress_2")); + ASSERT_TRUE(check_event_action_execute(pevents[3], "t_ingress_2", + "_clone_i2e")); + ASSERT_TRUE(check_event_table_miss(pevents[4], "t_egress")); + ASSERT_TRUE(check_event_action_execute(pevents[5], "t_egress", "_nop")); + ASSERT_TRUE(check_event_table_miss(pevents[6], "t_exit")); + ASSERT_TRUE(check_event_action_execute(pevents[7], "t_exit", "set_hdr")); + + events.get_and_remove_events(pid(1), &pevents, 4u); + ASSERT_EQ(4u, pevents.size()); + ASSERT_TRUE(check_event_table_miss(pevents[0], "t_egress")); + ASSERT_TRUE(check_event_action_execute(pevents[1], "t_egress", "_nop")); + ASSERT_TRUE(check_event_table_miss(pevents[2], "t_exit")); + ASSERT_TRUE(check_event_action_execute(pevents[3], "t_exit", "set_hdr")); + + events.get_and_remove_events(pid(2), &pevents, 4u); + ASSERT_EQ(4u, pevents.size()); + ASSERT_TRUE(check_event_table_miss(pevents[0], "t_egress")); + ASSERT_TRUE(check_event_action_execute(pevents[1], "t_egress", "_nop")); + ASSERT_TRUE(check_event_table_miss(pevents[2], "t_exit")); + ASSERT_TRUE(check_event_action_execute(pevents[3], "t_exit", "set_hdr")); +#endif +} + +class SimpleSwitch_PacketRedirectP4_CloneE2E_BMI + : public SimpleSwitch_PacketRedirectP4_BMI { + protected: + void add_entries(int port_out, int mirror_id) { + std::vector match_key_1; + match_key_1.emplace_back(MatchKeyParam::Type::EXACT, std::string("\x04")); + match_key_1.emplace_back(MatchKeyParam::Type::EXACT, + std::string("\x00", 1)); + ActionData data_1; + data_1.push_back_action_data(port_out); + entry_handle_t h_1; + ASSERT_EQ(MatchErrorCode::SUCCESS, + test_switch->mt_add_entry(0, "t_ingress_1", match_key_1, + "_set_port", std::move(data_1), &h_1)); + + std::vector match_key_2; + match_key_2.emplace_back(MatchKeyParam::Type::EXACT, std::string("\x04")); + // only PKT_INSTANCE_TYPE_NORMAL (= 0) + match_key_2.emplace_back(MatchKeyParam::Type::TERNARY, + std::string(4, '\x00'), std::string(4, '\xff')); + match_key_2.emplace_back(MatchKeyParam::Type::TERNARY, + std::string("\x00", 1), std::string("\xff", 1)); + ActionData data_2; + data_2.push_back_action_data(mirror_id); + entry_handle_t h_2; + ASSERT_EQ( + MatchErrorCode::SUCCESS, + test_switch->mt_add_entry(0, "t_egress", match_key_2, "_clone_e2e", + std::move(data_2), &h_2, 1)); + } +}; + +TEST_F(SimpleSwitch_PacketRedirectP4_CloneE2E_BMI, CloneE2E) { + static constexpr int port_in = 1; + static constexpr int port_out = 2; + static constexpr int port_out_copy = 3; + static constexpr int mirror_id = 1; + + add_entries(port_out, mirror_id); + + SimpleSwitch::MirroringSessionConfig config = {}; + config.egress_port = port_out_copy; + config.egress_port_valid = true; + test_switch->mirroring_add_session(mirror_id, config); + + const char pkt[] = {'\x04', '\x00', '\x00', '\x00'}; + packet_inject.send(port_in, pkt, sizeof(pkt)); + char recv_buffer[kMaxBufSize]; + int recv_port_1 = -1; + int recv_port_2 = -1; + receiver.read(recv_buffer, sizeof(pkt), &recv_port_1); + // check standard_metadata.packet_length + ASSERT_EQ(2, static_cast(recv_buffer[1])); + receiver.read(recv_buffer, sizeof(pkt), &recv_port_2); + ASSERT_EQ(2, static_cast(recv_buffer[1])); + // TODO(antonin): make sure the right packet comes out of the right port + ASSERT_TRUE((recv_port_1 == port_out && recv_port_2 == port_out_copy) || + (recv_port_1 == port_out_copy && recv_port_2 == port_out)); + + test_switch->mirroring_delete_session(mirror_id); + +#ifdef BM_ELOG_ON + // event check + std::vector pevents; + + events.get_and_remove_events(pid(0), &pevents, 8u); + ASSERT_EQ(8u, pevents.size()); + ASSERT_TRUE(check_event_table_hit(pevents[0], "t_ingress_1")); + ASSERT_TRUE(check_event_action_execute(pevents[1], "t_ingress_1", + "_set_port")); + ASSERT_TRUE(check_event_table_miss(pevents[2], "t_ingress_2")); + ASSERT_TRUE(check_event_action_execute(pevents[3], "t_ingress_2", "_nop")); + ASSERT_TRUE(check_event_table_hit(pevents[4], "t_egress")); + ASSERT_TRUE(check_event_action_execute(pevents[5], "t_egress", "_clone_e2e")); + ASSERT_TRUE(check_event_table_miss(pevents[6], "t_exit")); + ASSERT_TRUE(check_event_action_execute(pevents[7], "t_exit", "set_hdr")); + + events.get_and_remove_events(pid(1), &pevents, 4u); + ASSERT_EQ(4u, pevents.size()); + ASSERT_TRUE(check_event_table_miss(pevents[0], "t_egress")); + ASSERT_TRUE(check_event_action_execute(pevents[1], "t_egress", "_nop")); + ASSERT_TRUE(check_event_table_miss(pevents[2], "t_exit")); + ASSERT_TRUE(check_event_action_execute(pevents[3], "t_exit", "set_hdr")); +#endif +} + +TEST_F(SimpleSwitch_PacketRedirectP4_CloneE2E_BMI, CloneE2E_Multicast) { + static constexpr int port_in = 1; + static constexpr int port_out = 2; + static constexpr int port_out_copy = 3; + static constexpr int port_out_mc_copy = 4; + static constexpr int mirror_id = 1; + static constexpr int mgrp = 1; + + add_entries(port_out, mirror_id); + + SimpleSwitch::MirroringSessionConfig config = {}; + config.egress_port = port_out_copy; + config.egress_port_valid = true; + config.mgid = mgrp; + config.mgid_valid = true; + test_switch->mirroring_add_session(mirror_id, config); + + auto pre = test_switch->get_component(); + SCOPED_TRACE("SimpleSwitch_PacketRedirectP4_CloneE2E_BMI.CloneE2E_Multicast"); + PreTestConfiguration pre_config(pre.get(), mgrp, {port_out_mc_copy}); + + const char pkt[] = {'\x04', '\x00', '\x00', '\x00'}; + packet_inject.send(port_in, pkt, sizeof(pkt)); + char recv_buffer[kMaxBufSize]; + int recv_port_1 = -1, recv_port_2 = -1, recv_port_3 = -1; + receiver.read(recv_buffer, sizeof(pkt), &recv_port_1); + // check standard_metadata.packet_length + ASSERT_EQ(2, static_cast(recv_buffer[1])); + receiver.read(recv_buffer, sizeof(pkt), &recv_port_2); + ASSERT_EQ(2, static_cast(recv_buffer[1])); + receiver.read(recv_buffer, sizeof(pkt), &recv_port_3); + ASSERT_EQ(2, static_cast(recv_buffer[1])); + // TODO(antonin): make sure the right packet comes out of the right port + ASSERT_EQ(std::set({recv_port_1, recv_port_2, recv_port_3}), + std::set({port_out, port_out_copy, port_out_mc_copy})); + + test_switch->mirroring_delete_session(mirror_id); + +#ifdef BM_ELOG_ON + // event check + std::vector pevents; + + events.get_and_remove_events(pid(0), &pevents, 8u); + ASSERT_EQ(8u, pevents.size()); + ASSERT_TRUE(check_event_table_hit(pevents[0], "t_ingress_1")); + ASSERT_TRUE(check_event_action_execute(pevents[1], "t_ingress_1", + "_set_port")); + ASSERT_TRUE(check_event_table_miss(pevents[2], "t_ingress_2")); + ASSERT_TRUE(check_event_action_execute(pevents[3], "t_ingress_2", "_nop")); + ASSERT_TRUE(check_event_table_hit(pevents[4], "t_egress")); + ASSERT_TRUE(check_event_action_execute(pevents[5], "t_egress", "_clone_e2e")); + ASSERT_TRUE(check_event_table_miss(pevents[6], "t_exit")); + ASSERT_TRUE(check_event_action_execute(pevents[7], "t_exit", "set_hdr")); + + events.get_and_remove_events(pid(1), &pevents, 4u); + ASSERT_EQ(4u, pevents.size()); + ASSERT_TRUE(check_event_table_miss(pevents[0], "t_egress")); + ASSERT_TRUE(check_event_action_execute(pevents[1], "t_egress", "_nop")); + ASSERT_TRUE(check_event_table_miss(pevents[2], "t_exit")); + ASSERT_TRUE(check_event_action_execute(pevents[3], "t_exit", "set_hdr")); + + events.get_and_remove_events(pid(1), &pevents, 4u); + ASSERT_EQ(4u, pevents.size()); + ASSERT_TRUE(check_event_table_miss(pevents[0], "t_egress")); + ASSERT_TRUE(check_event_action_execute(pevents[1], "t_egress", "_nop")); + ASSERT_TRUE(check_event_table_miss(pevents[2], "t_exit")); + ASSERT_TRUE(check_event_action_execute(pevents[3], "t_exit", "set_hdr")); +#endif +} + +TEST_F(SimpleSwitch_PacketRedirectP4_BMI, Resubmit) { + /* In this test, the egress port is first set to 2, but because the packet is + selected for resubmission, and because of the resubmitted metadata, the + egress port will be set to 3 */ + static constexpr int port_in = 1; + static constexpr int port_out_1 = 2; + static constexpr int port_out_2 = 3; + + std::vector match_key_1; + match_key_1.emplace_back(MatchKeyParam::Type::EXACT, std::string("\x05")); + match_key_1.emplace_back(MatchKeyParam::Type::EXACT, std::string("\x00", 1)); + ActionData data_1; + data_1.push_back_action_data(port_out_1); + entry_handle_t h_1; + ASSERT_EQ(MatchErrorCode::SUCCESS, + test_switch->mt_add_entry(0, "t_ingress_1", match_key_1, + "_set_port", std::move(data_1), &h_1)); + + std::vector match_key_2; + match_key_2.emplace_back(MatchKeyParam::Type::EXACT, std::string("\x05")); + match_key_2.emplace_back(MatchKeyParam::Type::EXACT, std::string("\x01", 1)); + ActionData data_2; + data_2.push_back_action_data(port_out_2); + entry_handle_t h_2; + ASSERT_EQ(MatchErrorCode::SUCCESS, + test_switch->mt_add_entry(0, "t_ingress_1", match_key_2, + "_set_port", std::move(data_2), &h_2)); + + std::vector match_key_3; + match_key_3.emplace_back(MatchKeyParam::Type::EXACT, std::string("\x05")); + // only PKT_INSTANCE_TYPE_NORMAL (= 0) + match_key_3.emplace_back(MatchKeyParam::Type::TERNARY, + std::string(4, '\x00'), std::string(4, '\xff')); + ActionData data_3; + entry_handle_t h_3; + ASSERT_EQ(MatchErrorCode::SUCCESS, + test_switch->mt_add_entry(0, "t_ingress_2", match_key_3, + "_resubmit", std::move(data_3), &h_3, 1)); + + const char pkt[] = {'\x05', '\x00', '\x00', '\x00'}; + packet_inject.send(port_in, pkt, sizeof(pkt)); + char recv_buffer[kMaxBufSize]; + int recv_port = -1; + receiver.read(recv_buffer, sizeof(pkt), &recv_port); + // check standard_metadata.packet_length + ASSERT_EQ(2, static_cast(recv_buffer[1])); + ASSERT_EQ(port_out_2, recv_port); + +#ifdef BM_ELOG_ON + // event check + std::vector pevents; + + events.get_and_remove_events(pid(0), &pevents, 4u); + ASSERT_EQ(4u, pevents.size()); + ASSERT_TRUE(check_event_table_hit(pevents[0], "t_ingress_1")); + ASSERT_TRUE(check_event_action_execute(pevents[1], "t_ingress_1", + "_set_port")); + ASSERT_TRUE(check_event_table_hit(pevents[2], "t_ingress_2")); + ASSERT_TRUE(check_event_action_execute(pevents[3], "t_ingress_2", + "_resubmit")); + + // TODO(antonin): if we consider that it is the same packet, then the copy_id + // should be the same? Update this if this changes in simple_switch + events.get_and_remove_events(pid(1), &pevents, 8u); + ASSERT_EQ(8u, pevents.size()); + ASSERT_TRUE(check_event_table_hit(pevents[0], "t_ingress_1")); + ASSERT_TRUE(check_event_action_execute(pevents[1], "t_ingress_1", + "_set_port")); + ASSERT_TRUE(check_event_table_miss(pevents[2], "t_ingress_2")); + ASSERT_TRUE(check_event_action_execute(pevents[3], "t_ingress_2", "_nop")); + ASSERT_TRUE(check_event_table_miss(pevents[4], "t_egress")); + ASSERT_TRUE(check_event_action_execute(pevents[5], "t_egress", "_nop")); + ASSERT_TRUE(check_event_table_miss(pevents[6], "t_exit")); + ASSERT_TRUE(check_event_action_execute(pevents[7], "t_exit", "set_hdr")); +#endif +} + +TEST_F(SimpleSwitch_PacketRedirectP4_BMI, Recirculate) { + static constexpr int port_in = 1; + static constexpr int port_out_1 = 2; + static constexpr int port_out_2 = 3; + + std::vector match_key_1; + match_key_1.emplace_back(MatchKeyParam::Type::EXACT, std::string("\x06")); + match_key_1.emplace_back(MatchKeyParam::Type::EXACT, std::string("\x00", 1)); + ActionData data_1; + data_1.push_back_action_data(port_out_1); + entry_handle_t h_1; + ASSERT_EQ(MatchErrorCode::SUCCESS, + test_switch->mt_add_entry(0, "t_ingress_1", match_key_1, + "_set_port", std::move(data_1), &h_1)); + + std::vector match_key_2; + match_key_2.emplace_back(MatchKeyParam::Type::EXACT, std::string("\x06")); + match_key_2.emplace_back(MatchKeyParam::Type::EXACT, std::string("\x01", 1)); + ActionData data_2; + data_2.push_back_action_data(port_out_2); + entry_handle_t h_2; + ASSERT_EQ(MatchErrorCode::SUCCESS, + test_switch->mt_add_entry(0, "t_ingress_1", match_key_2, + "_set_port", std::move(data_2), &h_2)); + + std::vector match_key_3; + match_key_3.emplace_back(MatchKeyParam::Type::EXACT, std::string("\x06")); + // only PKT_INSTANCE_TYPE_NORMAL (= 0) + match_key_3.emplace_back(MatchKeyParam::Type::TERNARY, + std::string(4, '\x00'), std::string(4, '\xff')); + // only 0 + match_key_3.emplace_back(MatchKeyParam::Type::TERNARY, + std::string("\x00", 1), std::string("\xff", 1)); + ActionData data_3; + entry_handle_t h_3; + ASSERT_EQ(MatchErrorCode::SUCCESS, + test_switch->mt_add_entry(0, "t_egress", match_key_3, + "_recirculate", std::move(data_3), + &h_3, 1)); + + // recirc packet needs to be larger because of remove_header call + const char pkt[] = {'\x06', '\x00', '\x00', '\x00', '\x00', '\x00'}; + packet_inject.send(port_in, pkt, sizeof(pkt)); + char recv_buffer[kMaxBufSize]; + int recv_port = -1; + receiver.read(recv_buffer, sizeof(pkt), &recv_port); + // check standard_metadata.packet_length + ASSERT_EQ(2, static_cast(recv_buffer[1])); + ASSERT_EQ(port_out_2, recv_port); + +#ifdef BM_ELOG_ON + // event check + std::vector pevents; + + events.get_and_remove_events(pid(0), &pevents, 8u); + ASSERT_EQ(8u, pevents.size()); + ASSERT_TRUE(check_event_table_hit(pevents[0], "t_ingress_1")); + ASSERT_TRUE(check_event_action_execute(pevents[1], "t_ingress_1", + "_set_port")); + ASSERT_TRUE(check_event_table_miss(pevents[2], "t_ingress_2")); + ASSERT_TRUE(check_event_action_execute(pevents[3], "t_ingress_2", "_nop")); + ASSERT_TRUE(check_event_table_hit(pevents[4], "t_egress")); + ASSERT_TRUE(check_event_action_execute(pevents[5], "t_egress", + "_recirculate")); + ASSERT_TRUE(check_event_table_miss(pevents[6], "t_exit")); + ASSERT_TRUE(check_event_action_execute(pevents[7], "t_exit", "set_hdr")); + + // TODO(antonin): if we consider that it is the same packet, then the copy_id + // should be the same? Update this if this changes in simple_switch + events.get_and_remove_events(pid(1), &pevents, 8u); + ASSERT_EQ(8u, pevents.size()); + ASSERT_TRUE(check_event_table_hit(pevents[0], "t_ingress_1")); + ASSERT_TRUE(check_event_action_execute(pevents[1], "t_ingress_1", + "_set_port")); + ASSERT_TRUE(check_event_table_miss(pevents[2], "t_ingress_2")); + ASSERT_TRUE(check_event_action_execute(pevents[3], "t_ingress_2", "_nop")); + ASSERT_TRUE(check_event_table_miss(pevents[4], "t_egress")); + ASSERT_TRUE(check_event_action_execute(pevents[5], "t_egress", "_nop")); + ASSERT_TRUE(check_event_table_miss(pevents[6], "t_exit")); + ASSERT_TRUE(check_event_action_execute(pevents[7], "t_exit", "set_hdr")); +#endif +} + +TEST_F(SimpleSwitch_PacketRedirectP4_BMI, ExitIngress) { + static constexpr int port_in = 1; + static constexpr int port_out = 0; + + std::vector match_key_1; + match_key_1.emplace_back(MatchKeyParam::Type::EXACT, std::string("\x07")); + match_key_1.emplace_back(MatchKeyParam::Type::EXACT, std::string("\x00", 1)); + ActionData data_1; + entry_handle_t h_1; + ASSERT_EQ(MatchErrorCode::SUCCESS, + test_switch->mt_add_entry(0, "t_ingress_1", match_key_1, + "_exit", std::move(data_1), &h_1)); + + const char pkt[] = {'\x07', '\x00', '\x00', '\x00'}; + packet_inject.send(port_in, pkt, sizeof(pkt)); + char recv_buffer[kMaxBufSize]; + int recv_port = -1; + receiver.read(recv_buffer, sizeof(pkt), &recv_port); + ASSERT_EQ(port_out, recv_port); + +#ifdef BM_ELOG_ON + // event check + std::vector pevents; + + events.get_and_remove_events(pid(0), &pevents, 6u); + ASSERT_EQ(6u, pevents.size()); + ASSERT_TRUE(check_event_table_hit(pevents[0], "t_ingress_1")); + ASSERT_TRUE(check_event_action_execute(pevents[1], "t_ingress_1", "_exit")); + ASSERT_TRUE(check_event_table_miss(pevents[2], "t_egress")); + ASSERT_TRUE(check_event_action_execute(pevents[3], "t_egress", "_nop")); + ASSERT_TRUE(check_event_table_miss(pevents[4], "t_exit")); + ASSERT_TRUE(check_event_action_execute(pevents[5], "t_exit", "set_hdr")); +#endif +}