summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsongyanchao <[email protected]>2024-07-31 07:38:57 +0000
committersongyanchao <[email protected]>2024-08-09 09:31:48 +0000
commit7aed37ad50e7e1da4d56ccd5642b0bb823f764e1 (patch)
tree3df7db26ec6c73c085900c46f05ea41d05150091
parent15c83d74fed6280613dfa6856832dff3bcc31077 (diff)
🧪 test(DPISDN-52): Add CMocka-based test cases for BFD node.
Add CMocka-based test cases for BFD node.
-rw-r--r--infra/include/common.h31
-rw-r--r--service/CMakeLists.txt17
-rw-r--r--service/include/sc_metrics.h82
-rw-r--r--service/src/core.c10
-rw-r--r--service/src/node_bfd.c876
-rw-r--r--service/src/sc_metrics.c221
-rw-r--r--service/test/test_common.c167
-rw-r--r--service/test/test_node_bfd.c970
-rw-r--r--service/test/test_sc_metrics.c128
-rw-r--r--test/ptf_test/CMakeLists.txt2
10 files changed, 2266 insertions, 238 deletions
diff --git a/infra/include/common.h b/infra/include/common.h
index 881ee2d..f3fd99d 100644
--- a/infra/include/common.h
+++ b/infra/include/common.h
@@ -376,6 +376,37 @@ struct g_vxlan_hdr
#define G_VXLAN_INNER_LINK_TYPE_PPP (0x8)
#define G_VXLAN_INNER_LINK_TYPE_HDLC (0xC)
+/* =============================== BFD_DEFINE =============================== */
+
+struct bfd_hdr
+{
+ uint8_t version_diag;
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+ uint8_t flags : 6;
+ uint8_t state : 2;
+#elif __BYTE_ORDER == __BIG_ENDIAN
+ uint8_t state : 2;
+ uint8_t flags : 6;
+#endif
+
+ uint8_t detect_time_multiplier;
+ uint8_t length;
+ uint8_t my_discriminator[4];
+ uint8_t your_discriminator[4];
+ uint8_t desired_min_tx_interval[4];
+ uint8_t required_min_rx_interval[4];
+ uint8_t required_min_echo_interval[4];
+};
+
+/* Bfd State */
+enum
+{
+ BFD_STATE_ADMINDOWN = 0,
+ BFD_STATE_DOWN,
+ BFD_STATE_INIT,
+ BFD_STATE_UP
+};
+
static inline int mr_thread_setname(pthread_t id, const char * name)
{
// name length less 16 characters
diff --git a/service/CMakeLists.txt b/service/CMakeLists.txt
index e4d9d04..d33d97d 100644
--- a/service/CMakeLists.txt
+++ b/service/CMakeLists.txt
@@ -2,7 +2,7 @@
include_directories(include)
set(MRZCPD_SERVICE_SOURCE_FILES src/core.c src/devmgr.c src/mrb.c src/hwinfo.c src/vdev.c
- src/vdata.c src/app.c src/monit.c src/node.c src/node_shmdev.c
+ src/vdata.c src/app.c src/monit.c src/node.c src/sc_metrics.c src/node_shmdev.c
src/node_phydev.c src/node_lb.c src/node_classifier.c src/node_etherfabric.c src/node_tera.c
src/node_eth_ingress.c src/node_eth_egress.c src/node_bfd.c src/node_vwire.c
src/node_health_check.c src/node_forwarder.c src/node_bridge.c src/pdump.c src/olp.c src/olp_6500.c
@@ -44,3 +44,18 @@ target_link_libraries(test_trace_service MESA_prof_load_static libcmocka rt pthr
target_link_options(test_trace_service PRIVATE -Wl,--wrap=rte_mp_reply,--wrap=app_event_handler_register,--wrap=app_lookup_by_symbol)
target_link_options(test_trace_service PRIVATE -Wl,--wrap=sc_main_get)
add_test(NAME test_trace_service COMMAND test_trace_service)
+
+add_executable(test_node_bfd test/test_node_mocks.c test/test_node_bfd.c test/test_common.c ${MRZCPD_SERVICE_SOURCE_FILES})
+target_compile_options(test_node_bfd PRIVATE --coverage -fprofile-arcs -ftest-coverage)
+target_link_libraries(test_node_bfd MESA_prof_load_static ${SYSTEMD_LIBRARIES} libdpdk)
+target_link_libraries(test_node_bfd libcmocka rt pthread dl infra z elf)
+target_link_options(test_node_bfd PRIVATE --coverage -fprofile-arcs -ftest-coverage)
+target_include_directories(test_node_bfd PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include/")
+
+
+add_executable(test_sc_metrics test/test_node_mocks.c test/test_sc_metrics.c ${MRZCPD_SERVICE_SOURCE_FILES})
+target_compile_options(test_sc_metrics PRIVATE --coverage -fprofile-arcs -ftest-coverage)
+target_link_libraries(test_sc_metrics MESA_prof_load_static ${SYSTEMD_LIBRARIES} libdpdk)
+target_link_libraries(test_sc_metrics libcmocka rt pthread dl infra z elf)
+target_link_options(test_sc_metrics PRIVATE --coverage -fprofile-arcs -ftest-coverage)
+target_include_directories(test_sc_metrics PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include/")
diff --git a/service/include/sc_metrics.h b/service/include/sc_metrics.h
new file mode 100644
index 0000000..4260564
--- /dev/null
+++ b/service/include/sc_metrics.h
@@ -0,0 +1,82 @@
+/**
+ * @file sc_metrics.h
+ * @brief Header file for SC metrics handle management.
+ *
+ * This header file defines the data structures and function prototypes for managing
+ * SC metrics handles in the system. The SC metrics handle is used to store and process
+ * metric values, with functions provided for creating, deleting, and manipulating
+ * these handles. Key operations include setting, resetting, and accumulating metric values.
+ *
+ * The following data structures and functions are defined:
+ *
+ * Data Structures:
+ * - `struct sc_metrics_handle`: Represents a metrics handle with a capacity and a pointer
+ * to an array of metric values.
+ *
+ * Functions:
+ * - `sc_metrics_handle_create(uint16_t capacity)`: Allocates and initializes a new SC metrics
+ * handle with the specified capacity, which is rounded up to the nearest multiple of 8.
+ *
+ * - `sc_metrics_handle_delete(struct sc_metrics_handle * metrics_handle)`: Deallocates the memory
+ * associated with an SC metrics handle, including the values array.
+ *
+ * - `sc_metrics_values_set(struct sc_metrics_handle * metrics_handle, uint64_t values[], uint16_t capacity)`:
+ * Sets the metric values in the handle. The provided capacity must not exceed the handle's allocated
+ * capacity.
+ *
+ * - `sc_metrics_values_reset(struct sc_metrics_handle * metrics_handle)`: Resets all metric values
+ * in the handle to zero.
+ *
+ * - `sc_metrics_accumulate(struct sc_metrics_handle * metrics_handle, uint64_t inc_values[], uint16_t capacity)`:
+ * Accumulates new values into the existing metric values within the handle. The provided capacity
+ * must not exceed the handle's allocated capacity.
+ */
+
+#pragma once
+
+#include <common.h>
+
+#define SC_METRICS_MAX_CAPACITY 32
+
+struct sc_metrics_handle
+{
+ uint16_t capacity;
+ volatile uint64_t * values;
+};
+
+struct sc_metrics_handle * sc_metrics_handle_create(uint16_t capacity);
+int sc_metrics_handle_delete(struct sc_metrics_handle * metrics_handle);
+int sc_metrics_value_get(struct sc_metrics_handle * metrics_handle, uint64_t * value, uint16_t key);
+int sc_metrics_values_get(struct sc_metrics_handle * metrics_handle, uint64_t values[], uint16_t capacity);
+static inline int sc_metrics_value_set(struct sc_metrics_handle * metrics_handle, uint64_t value, uint16_t key);
+int sc_metrics_values_set(struct sc_metrics_handle * metrics_handle, uint64_t values[], uint16_t capacity);
+int sc_metrics_values_reset(struct sc_metrics_handle * metrics_handle);
+int sc_metrics_accumulate(struct sc_metrics_handle * metrics_handle, uint64_t inc_values[], uint16_t capacity);
+
+/**
+ * @brief Set a single value for the SC metrics handle.
+ *
+ * This function sets a single metric value in the SC metrics handle at the
+ * specified key. The function ensures that the key is within the handle's
+ * allocated capacity. The value is directly written to the handle's values
+ * array at the specified key.
+ *
+ * @param metrics_handle Pointer to the SC metrics handle where the value will
+ * be set. This handle must be valid and initialized.
+ * @param value The metric value to set in the handle.
+ * @param key The key at which to set the metric value. This key must be within
+ * the handle's allocated capacity.
+ *
+ * @return Return value indicating success:
+ * - RT_SUCCESS (0) if the value is successfully set.
+ */
+static inline int sc_metrics_value_set(struct sc_metrics_handle * metrics_handle, uint64_t value, uint16_t key)
+{
+ assert(metrics_handle != NULL);
+ assert(metrics_handle->values != NULL);
+ assert(key < metrics_handle->capacity);
+
+ metrics_handle->values[key] = value;
+
+ return RT_SUCCESS;
+}
diff --git a/service/src/core.c b/service/src/core.c
index d79a176..111a3b4 100644
--- a/service/src/core.c
+++ b/service/src/core.c
@@ -932,6 +932,7 @@ extern int mr_pdump_init(struct sc_main * sc);
extern int http_serv_init(struct sc_main * sc_main);
extern int olp_manager_init(struct sc_main * sc_main);
extern int lai_init(struct sc_main * sc);
+extern int bfd_init(struct sc_main * sc);
int marsio_service_main(int argc, char * argv[])
{
@@ -1193,7 +1194,7 @@ int marsio_service_main(int argc, char * argv[])
if (mr_pdump_init(sc) != RT_SUCCESS)
{
- MR_ERROR("mrpdump initialization failed. ");
+ MR_ERROR("Mrpdump initialization failed. ");
ret = EXIT_FAILURE;
goto quit;
}
@@ -1205,6 +1206,13 @@ int marsio_service_main(int argc, char * argv[])
goto quit;
}
+ if (bfd_init(sc) != RT_SUCCESS)
+ {
+ MR_ERROR("BFD initialization failed. ");
+ ret = EXIT_FAILURE;
+ goto quit;
+ }
+
sc_config_dump(sc);
vdev_dump(sc);
port_adapter_mapping_dump();
diff --git a/service/src/node_bfd.c b/service/src/node_bfd.c
index 5a8e812..aa0fa9c 100644
--- a/service/src/node_bfd.c
+++ b/service/src/node_bfd.c
@@ -1,18 +1,32 @@
-#include "common.h"
#include <cJSON.h>
+
+#include <rte_branch_prediction.h>
#include <rte_graph.h>
#include <rte_graph_worker.h>
+#include <MESA_prof_load.h>
+#include <common.h>
#include <sc_common.h>
+#include <sc_metrics.h>
#include <sc_node_common.h>
#include <sc_trace.h>
-#include <stdint.h>
-/* Global Config */
-#define MR_BFD_MAX_SESSION_NUM 128
-#define MR_BFD_START_DISCRIMINATOR 1000
+/* Global bfd configuration */
+#define MR_BFD_MAX_SESSION_NUM 128 // Maximum number of BFD sessions
+#define MR_BFD_START_DISCRIMINATOR 1000 // Starting value for BFD discriminators
-/* Bfd Next Node */
+/*************************************************** Bfd node main ****************************************************/
+/* Bfd node metrics */
+enum bfd_node_metrics_key
+{
+ BFD_METRIC_TOT_PKTS = 0, // Total packets
+ BFD_METRIC_PKTS_BATCH, // Packets per batch
+ BFD_METRIC_DROP_UNEXP_PKTS, // Dropped unexpected packets
+ BFD_METRIC_DROP_NO_FREE_SESS_PKTS, // Dropped packets due to no free session ID
+ BFD_METRIC_MAX // Maximum metrics count
+};
+
+/* Bfd next node */
enum
{
BFD_NEXT_PKT_DROP = 0,
@@ -20,91 +34,466 @@ enum
BFD_NEXT_MAX
};
-/* Bfd State */
-enum
+/* BFD discriminator processing method */
+enum bfd_discriminator_method
{
- BFD_STATE_ADMINDOWN = 0,
- BFD_STATE_DOWN,
- BFD_STATE_INIT,
- BFD_STATE_UP
+ BFD_DISCRIMINATOR_METHOD_RFC_STANDARD = 0, // Based on RFC standard
+ BFD_DISCRIMINATOR_METHOD_FLIPPING, // Flipping the discriminator
+ BFD_DISCRIMINATOR_METHOD_MAX
};
-struct bfd_header_t
+/* Bfd node main struct */
+struct bfd_node_main
{
- uint8_t version_diag;
-#if __BYTE_ORDER == __LITTLE_ENDIAN
- uint8_t flags : 6;
- uint8_t state : 2;
-#elif __BYTE_ORDER == __BIG_ENDIAN
- uint8_t state : 2;
- uint8_t flags : 6;
-#endif
-
- uint8_t detect_time_multiplier;
- uint8_t length;
- uint8_t my_discriminator[4];
- uint8_t your_discriminator[4];
- uint8_t desired_min_tx_interval[4];
- uint8_t required_min_rx_interval[4];
- uint8_t required_min_echo_interval[4];
+ /* Number of graphs */
+ uint16_t nr_graphs;
+
+ /* Discriminator processing method */
+ enum bfd_discriminator_method discriminator_method;
+
+ /* Bfd session handles */
+ struct bfd_session_handle ** bfd_session_handles;
+
+ /* Bfd metrics handles */
+ struct sc_metrics_handle ** metrics_handles;
};
-/* Bfd Stat Field */
-struct bfd_stats_for_session
+/* Global variable for BFD node management */
+static struct bfd_node_main * g_bfd_main = NULL; // Pointer to the main BFD node structure
+
+extern struct bfd_session_handle * bfd_session_handle_create(uint16_t max_sessions);
+int bfd_session_handle_delete(struct bfd_session_handle * bfd_session_handle);
+
+/**
+ * @brief Delete the BFD node main struct.
+ *
+ * This function frees the memory allocated for the BFD node main struct.
+ * Note: This function is not thread-safe.
+ *
+ * @param bfd_node_main Pointer to the BFD node main struct to delete.
+ */
+int bfd_node_main_delete(struct bfd_node_main * bfd_node_main)
+{
+ if (bfd_node_main == NULL)
+ {
+ return RT_ERR;
+ }
+
+ for (uint16_t i = 0; i < bfd_node_main->nr_graphs; i++)
+ {
+ bfd_session_handle_delete(bfd_node_main->bfd_session_handles[i]);
+ sc_metrics_handle_delete(bfd_node_main->metrics_handles[i]);
+ }
+
+ FREE(bfd_node_main);
+ return RT_SUCCESS;
+}
+
+/**
+ * @brief Create and initialize the BFD node main struct.
+ *
+ * This function allocates memory for the BFD node main struct and initializes it.
+ * Note: This function is not thread-safe.
+ *
+ * @param nr_graphs The number of graphs to create.
+ * @return Pointer to the allocated and initialized BFD node main struct, or NULL on failure.
+ */
+struct bfd_node_main * bfd_node_main_create(uint16_t nr_graphs)
+{
+ if (unlikely(nr_graphs == 0))
+ {
+ MR_ERROR("Error creating bfd node main: invalid number of graphs.");
+ return NULL;
+ }
+
+ struct bfd_node_main * bfd_node_main = ZMALLOC(sizeof(struct bfd_node_main));
+ MR_VERIFY_MALLOC(bfd_node_main);
+
+ bfd_node_main->nr_graphs = nr_graphs;
+
+ bfd_node_main->bfd_session_handles = (struct bfd_session_handle **)ZMALLOC(sizeof(struct bfd_session_handle *) *
+ bfd_node_main->nr_graphs);
+ MR_VERIFY_MALLOC(bfd_node_main->bfd_session_handles);
+
+ bfd_node_main->metrics_handles = (struct sc_metrics_handle **)ZMALLOC(sizeof(struct sc_metrics_handle *) *
+ bfd_node_main->nr_graphs);
+ MR_VERIFY_MALLOC(bfd_node_main->metrics_handles);
+
+ for (uint16_t i = 0; i < bfd_node_main->nr_graphs; i++)
+ {
+ bfd_node_main->bfd_session_handles[i] = bfd_session_handle_create(MR_BFD_MAX_SESSION_NUM);
+ bfd_node_main->metrics_handles[i] = sc_metrics_handle_create(BFD_METRIC_MAX);
+
+ if ((bfd_node_main->bfd_session_handles[i] == NULL) || (bfd_node_main->metrics_handles[i] == NULL))
+ {
+ bfd_node_main_delete(bfd_node_main);
+ return NULL;
+ }
+ }
+
+ return bfd_node_main;
+}
+
+/**
+ * @brief Retrieve the global BFD node main instance.
+ *
+ * This function returns a pointer to the global BFD node main structure.
+ *
+ * @return Pointer to the global BFD node main structure.
+ */
+struct bfd_node_main * g_bfd_node_main_get(void)
{
- volatile uint64_t rx_pkts;
- volatile uint64_t prev_tsc;
- uint32_t ip_addr;
-} __rte_cache_aligned;
+ return g_bfd_main;
+}
-/* Bfd Stat Struct */
-struct bfd_stats
+/**
+ * @brief Set the global BFD node main instance.
+ *
+ * This function sets the global BFD node main structure.
+ *
+ * @param bfd_node_main Pointer to the BFD node main struct.
+ */
+int g_bfd_node_main_set(struct bfd_node_main * bfd_node_main)
{
- volatile uint64_t total_pkts;
- volatile uint64_t pkts_per_batch;
- volatile uint64_t drop_for_unexpected_pkts;
- volatile uint64_t drop_for_no_free_session_id;
- struct bfd_stats_for_session stats_for_session[MR_BFD_MAX_SESSION_NUM];
-} __rte_cache_aligned;
-
-static struct bfd_stats stats_per_graph[RTE_MAX_LCORE] = {};
-
-/************************************** Bfd Node **************************************/
-/* Get Free Session Id */
-uint16_t get_free_session_id(rte_graph_t graph_id, struct rte_ipv4_hdr * ipv4_hdr)
+ g_bfd_main = bfd_node_main;
+ return RT_SUCCESS;
+}
+
+/**
+ * @brief Initialize the BFD node.
+ *
+ * This function initializes the BFD node by creating the BFD node main struct.
+ *
+ * @param sc Pointer to the SC main struct.
+ * @return RT_SUCCESS on success, RT_ERR on failure.
+ */
+int bfd_init(struct sc_main * sc)
{
- struct bfd_stats * stats = &stats_per_graph[graph_id];
+ struct bfd_node_main * bfd_node_main = bfd_node_main_create(sc->nr_io_thread);
- for (int i = 0; i < MR_BFD_MAX_SESSION_NUM; i++)
+ if (bfd_node_main == NULL)
{
- if (stats->stats_for_session[i].ip_addr != 0)
- continue;
+ return RT_ERR;
+ }
+
+ unsigned int discriminator_method;
+ MESA_load_profile_uint_def(sc->local_cfgfile, "bfd", "disc_method", &discriminator_method,
+ BFD_DISCRIMINATOR_METHOD_RFC_STANDARD);
+
+ if (discriminator_method >= BFD_DISCRIMINATOR_METHOD_MAX)
+ {
+ MR_ERROR("Error initializing bfd node: invalid discriminator method.");
+ bfd_node_main_delete(bfd_node_main);
+ return RT_ERR;
+ }
+
+ bfd_node_main->discriminator_method = discriminator_method;
+ g_bfd_node_main_set(bfd_node_main);
+
+ MR_INFO("Bfd init success");
+ if (discriminator_method == 0)
+ MR_INFO("Bfd discriminator method: RFC Standard");
+ else
+ MR_INFO("Bfd discriminator method: Flipping");
+
+ return RT_SUCCESS;
+}
+
+/**
+ * @brief Deinitialize the BFD node.
+ *
+ * This function deinitializes the BFD node by deleting the BFD node main struct.
+ *
+ * @param sc Pointer to the SC main struct.
+ */
+
+void bfd_deinit(struct sc_main * sc)
+{
+ bfd_node_main_delete(g_bfd_main);
+ g_bfd_node_main_set(NULL);
+}
+
+/************************************************* Bfd session handle *************************************************/
+/* BFD session structure */
+struct bfd_session
+{
+ volatile uint64_t prev_tsc; // Timestamp of the previous BFD packet
+ uint32_t ipv4_address; // IPv4 address of the remote peer
+ uint32_t my_discriminator; // Discriminator assigned to this session
+ uint64_t reserved[6]; // Reserved for future use or alignment
+} __rte_cache_aligned; // Ensure the structure is cache-aligned
+
+/* BFD session handle structure */
+struct bfd_session_handle
+{
+ struct bfd_session * bfd_sessions; // Pointer to an array of BFD sessions
+ uint16_t max_sessions; // Maximum number of sessions supported
+ uint16_t current_sessions; // Number of active sessions currently
+};
+
+/**
+ * @brief Create and initialize a BFD session handle.
+ *
+ * This function allocates memory for the BFD session handle and initializes it.
+ * Note: This function is not thread-safe.
+ *
+ * @param max_sessions The maximum number of sessions that can be stored.
+ * @return Pointer to the allocated and initialized session handle, or NULL on failure.
+ */
+struct bfd_session_handle * bfd_session_handle_create(uint16_t max_sessions)
+{
+ if (unlikely(max_sessions == 0))
+ {
+ MR_ERROR("Error creating bfd session handle: invalid number of sessions.");
+ return NULL;
+ }
+
+ struct bfd_session_handle * bfd_session_handle = (struct bfd_session_handle *)ZMALLOC(
+ sizeof(struct bfd_session_handle));
+ MR_VERIFY_MALLOC(bfd_session_handle);
+
+ bfd_session_handle->bfd_sessions = (struct bfd_session *)ZMALLOC(sizeof(struct bfd_session) * max_sessions);
+ MR_VERIFY_MALLOC(bfd_session_handle->bfd_sessions);
+
+ bfd_session_handle->max_sessions = max_sessions;
+ bfd_session_handle->current_sessions = 0;
+
+ return bfd_session_handle;
+}
+
+/**
+ * @brief Delete a BFD session handle.
+ *
+ * This function frees the memory allocated for the BFD session handle.
+ * Note: This function is not thread-safe.
+ *
+ * @param bfd_session_handle Pointer to the BFD session handle to delete.
+ */
+int bfd_session_handle_delete(struct bfd_session_handle * bfd_session_handle)
+{
+ if (bfd_session_handle == NULL)
+ return RT_ERR;
+
+ FREE(bfd_session_handle->bfd_sessions);
+ bfd_session_handle->bfd_sessions = NULL;
+
+ FREE(bfd_session_handle);
+ return RT_SUCCESS;
+}
+
+/**
+ * @brief Get the BFD session handle.
+ *
+ * This function returns the BFD session handle for the specified graph ID.
+ *
+ * @param bfd_node_main Pointer to the BFD node main struct.
+ * @param graph_id The graph ID.
+ * @return Pointer to the BFD session handle.
+ */
+struct bfd_session_handle * bfd_session_handle_get(struct bfd_node_main * bfd_node_main, uint16_t graph_id)
+{
+ assert(bfd_node_main != NULL);
+ assert(graph_id < bfd_node_main->nr_graphs);
+
+ return bfd_node_main->bfd_session_handles[graph_id];
+}
+
+/**
+ * @brief Check if a BFD session handle is empty.
+ *
+ * This function checks if a BFD session handle is empty.
+ *
+ * @param bfd_session_handle Pointer to the BFD session handle.
+ * @return 1 if the session handle is empty, 0 otherwise.
+ */
+int bfd_session_handle_is_empty(struct bfd_session_handle * bfd_session_handle)
+{
+ assert(bfd_session_handle != NULL);
+
+ return bfd_session_handle->current_sessions == 0;
+}
+
+/**
+ * @brief Check if a BFD session handle is full.
+ *
+ * This function checks if a BFD session handle is full.
+ *
+ * @param bfd_session_handle Pointer to the BFD session handle.
+ * @return 1 if the session handle is full, 0 otherwise.
+ */
+static inline int __bfd_session_handle_is_full(struct bfd_session_handle * bfd_session_handle)
+{
+ assert(bfd_session_handle != NULL);
+
+ return bfd_session_handle->current_sessions == bfd_session_handle->max_sessions;
+}
- stats->stats_for_session[i].ip_addr = ipv4_hdr->src_addr;
- return i;
+/**
+ * @brief Check if a BFD session handle is full.
+ *
+ * This function checks if a BFD session handle is full.
+ *
+ * @param bfd_session_handle Pointer to the BFD session handle.
+ * @return 1 if the session handle is full, 0 otherwise.
+ */
+int bfd_session_handle_is_full(struct bfd_session_handle * bfd_session_handle)
+{
+ return __bfd_session_handle_is_full(bfd_session_handle);
+}
+
+/**
+ * @brief Lookup for a BFD session by IPv4 address.
+ *
+ * This function lookups for a BFD session with the specified IPv4 address.
+ * Note: This function is not thread-safe.
+ *
+ * @param bfd_session_handle Pointer to the BFD session handle.
+ * @param ipv4_address The IPv4 address to lookup for.
+ * @return Pointer to the BFD session with the specified IPv4 address, or NULL if not found.
+ */
+struct bfd_session * bfd_session_lookup(struct bfd_session_handle * bfd_session_handle, uint32_t ipv4_address)
+{
+ assert(bfd_session_handle != NULL);
+
+ struct bfd_session * bfd_session = NULL;
+ for (uint16_t i = 0; i < bfd_session_handle->max_sessions; i++)
+ {
+ bfd_session = &bfd_session_handle->bfd_sessions[i];
+ if (bfd_session->ipv4_address == ipv4_address)
+ {
+ return &bfd_session_handle->bfd_sessions[i];
+ }
}
- return UINT16_MAX;
+
+ return NULL;
}
-/* Get Bfd Session Id */
-uint16_t get_bfd_session_id(rte_graph_t graph_id, struct rte_ipv4_hdr * ipv4_hdr)
+/**
+ * @brief Add a BFD session to the session handle.
+ *
+ * This function adds a BFD session to the session handle.
+ * Note: This function is not thread-safe.
+ *
+ * @param bfd_session_handle Pointer to the BFD session handle.
+ * @param ipv4_address The IPv4 address of the session to add.
+ * @return Pointer to the added BFD session, or NULL if no free session ID.
+ */
+struct bfd_session * bfd_session_add(struct bfd_session_handle * bfd_session_handle, uint32_t ipv4_address)
{
- struct bfd_stats * stats = &stats_per_graph[graph_id];
+ assert(bfd_session_handle != NULL);
- for (int i = 0; i < MR_BFD_MAX_SESSION_NUM; i++)
+ if (unlikely(__bfd_session_handle_is_full(bfd_session_handle)))
{
- if (stats->stats_for_session[i].ip_addr == ipv4_hdr->src_addr)
- return i;
+ MR_ERROR("Error adding bfd session: no free session id.");
+ return NULL;
}
- return get_free_session_id(graph_id, ipv4_hdr);
+ struct bfd_session * bfd_session = NULL;
+ for (uint16_t i = 0; i < bfd_session_handle->max_sessions; i++)
+ {
+ bfd_session = &bfd_session_handle->bfd_sessions[i];
+ if (bfd_session->ipv4_address == 0)
+ {
+ bfd_session->ipv4_address = ipv4_address;
+ bfd_session->prev_tsc = 0;
+ bfd_session->my_discriminator = htonl(MR_BFD_START_DISCRIMINATOR + i);
+
+ bfd_session_handle->current_sessions++;
+
+ return bfd_session;
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * @brief Update a BFD session.
+ *
+ * This function updates a BFD session.
+ * Note: This function is not thread-safe.
+ *
+ * @param bfd_session Pointer to the BFD session to update.
+ */
+static inline int __bfd_session_update(struct bfd_session * bfd_session)
+{
+ assert(bfd_session != NULL);
+
+ bfd_session->prev_tsc = rte_rdtsc();
+ return RT_SUCCESS;
}
-#define BFD_USE_YOUR_DISCRIMINATOR_AS_MY_DISCRIMINATOR 0
+/**
+ * @brief Update a BFD session.
+ *
+ * This function updates a BFD session.
+ * Note: This function is not thread-safe.
+ *
+ * @param bfd_session Pointer to the BFD session to update.
+ */
+int bfd_session_update(struct bfd_session * bfd_session)
+{
+ return __bfd_session_update(bfd_session);
+}
-/* Reply Bfd Request */
-uint8_t bfd_reply(struct rte_ether_hdr * ether_hdr, struct rte_ipv4_hdr * ipv4_hdr, struct rte_udp_hdr * udp_hdr,
- uint16_t bfd_session_id)
+/**
+ * @brief Delete a BFD session from the session handle.
+ *
+ * This function deletes a BFD session from the session handle.
+ * Note: This function is not thread-safe.
+ *
+ * @param bfd_session_handle Pointer to the BFD session handle.
+ * @param ipv4_address The IPv4 address of the session to delete.
+ */
+int bfd_session_delete(struct bfd_session_handle * bfd_session_handle, uint32_t ipv4_address)
+{
+ assert(bfd_session_handle != NULL);
+
+ if (unlikely(bfd_session_handle_is_empty(bfd_session_handle)))
+ {
+ MR_ERROR("Error deleting bfd session: session handle is empty.");
+ return RT_ERR;
+ }
+
+ struct bfd_session * bfd_session = NULL;
+ for (uint16_t i = 0; i < bfd_session_handle->max_sessions; i++)
+ {
+ bfd_session = &bfd_session_handle->bfd_sessions[i];
+ if (bfd_session->ipv4_address == ipv4_address)
+ {
+ memset(bfd_session, 0, sizeof(struct bfd_session));
+ bfd_session_handle->current_sessions--;
+ return RT_SUCCESS;
+ }
+ }
+
+ return RT_ERR;
+}
+
+/************************************************ Auxiliary functions *************************************************/
+/**
+ * @brief Prepare a BFD packet reply by swapping headers and updating checksums.
+ *
+ * This function processes an incoming BFD packet and prepares a reply by performing
+ * the following steps:
+ * - Swap the source and destination Ethernet addresses in the Ethernet header.
+ * - Swap the source and destination IP addresses in the IPv4 header, and reset the TTL.
+ * - Recalculate the IPv4 header checksum.
+ * - Swap the discriminators in the BFD header based on the configuration flag
+ * `BFD_USE_YOUR_DISCRIMINATOR_AS_MY_DISCRIMINATOR`.
+ * - Reset the UDP checksum.
+ * - Update the BFD state based on the current state and transition it appropriately.
+ *
+ * @param ether_hdr Pointer to the Ethernet header.
+ * @param ipv4_hdr Pointer to the IPv4 header.
+ * @param udp_hdr Pointer to the UDP header.
+ * @param bfd_hdr Pointer to the BFD header.
+ * @param bfd_session Pointer to the BFD session structure.
+ *
+ * @return The previous BFD state.
+ */
+uint8_t bfd_pkt_reply(struct rte_ether_hdr * ether_hdr, struct rte_ipv4_hdr * ipv4_hdr, struct rte_udp_hdr * udp_hdr,
+ struct bfd_hdr * bfd_hdr, struct bfd_session * bfd_session)
{
/* Swap ether_hdr */
struct rte_ether_addr swap_addr;
@@ -123,24 +512,24 @@ uint8_t bfd_reply(struct rte_ether_hdr * ether_hdr, struct rte_ipv4_hdr * ipv4_h
ipv4_hdr->hdr_checksum = rte_ipv4_cksum(ipv4_hdr);
/* Swap discriminator */
- struct bfd_header_t * bfd_hdr = (struct bfd_header_t *)(udp_hdr + 1);
- uint8_t swap_discriminator[4];
-
-#if BFD_USE_YOUR_DISCRIMINATOR_AS_MY_DISCRIMINATOR
- memcpy(swap_discriminator, bfd_hdr->your_discriminator, sizeof(swap_discriminator));
- memcpy(bfd_hdr->your_discriminator, bfd_hdr->my_discriminator, sizeof(swap_discriminator));
- memcpy(bfd_hdr->my_discriminator, swap_discriminator, sizeof(swap_discriminator));
-#else
- uint32_t discriminator = htonl(MR_BFD_START_DISCRIMINATOR + bfd_session_id);
- memcpy(swap_discriminator, bfd_hdr->my_discriminator, sizeof(swap_discriminator));
- memcpy(bfd_hdr->my_discriminator, &discriminator, sizeof(swap_discriminator));
- memcpy(bfd_hdr->your_discriminator, swap_discriminator, sizeof(swap_discriminator));
-#endif
+
+ if (g_bfd_main->discriminator_method == BFD_DISCRIMINATOR_METHOD_RFC_STANDARD)
+ {
+ rte_memcpy(bfd_hdr->your_discriminator, bfd_hdr->my_discriminator, sizeof(bfd_hdr->my_discriminator));
+ rte_memcpy(bfd_hdr->my_discriminator, &bfd_session->my_discriminator, sizeof(bfd_hdr->my_discriminator));
+ }
+ else
+ {
+ uint8_t swap_discriminator[4];
+ rte_memcpy(swap_discriminator, bfd_hdr->your_discriminator, sizeof(swap_discriminator));
+ rte_memcpy(bfd_hdr->your_discriminator, bfd_hdr->my_discriminator, sizeof(swap_discriminator));
+ rte_memcpy(bfd_hdr->my_discriminator, swap_discriminator, sizeof(swap_discriminator));
+ }
/* Set udp check sum */
udp_hdr->dgram_cksum = 0;
- /* Set Bfd State */
+ /* Set Bfd state */
uint8_t bfd_state = bfd_hdr->state;
if (bfd_state == BFD_STATE_DOWN)
{
@@ -156,15 +545,8 @@ uint8_t bfd_reply(struct rte_ether_hdr * ether_hdr, struct rte_ipv4_hdr * ipv4_h
return bfd_state;
}
-/* Bfd Node Init Function */
-static int bfd_node_init(const struct rte_graph * graph, struct rte_node * node)
-{
- return 0;
-}
-
-static __rte_always_inline void gen_store_trace_info(struct rte_node * node, struct rte_mbuf * mbuf,
- uint16_t next_node_index, int expect_result,
- uint16_t bfd_session_id, uint8_t bfd_state)
+void gen_store_trace_info(struct rte_node * node, struct rte_mbuf * mbuf, uint16_t next_node_index, int expect_result,
+ uint8_t bfd_state, struct bfd_session * bfd_session)
{
/* Populate the next node infomation */
char str_record[MR_STRING_MAX];
@@ -177,7 +559,7 @@ static __rte_always_inline void gen_store_trace_info(struct rte_node * node, str
{
len += snprintf(str_record + len, sizeof(str_record) - len, ", rsn:pkt noncompliant with bfd");
}
- else if (bfd_session_id == UINT16_MAX)
+ else if (bfd_session == NULL)
{
len += snprintf(str_record + len, sizeof(str_record) - len, ", rsn:no free sessionid");
}
@@ -192,132 +574,22 @@ static __rte_always_inline void gen_store_trace_info(struct rte_node * node, str
dp_trace_record_emit_str(sc_main_get()->trace, mbuf, rte_lcore_id(), &meta, str_record);
}
-/* Bfd Node Process Function */
-static __rte_always_inline uint16_t bfd_node_process(struct rte_graph * graph, struct rte_node * node, void ** objs,
- uint16_t cnt)
-{
- uint16_t last_spec = 0, next_node_index = 0;
- uint16_t n_left_from = cnt;
- struct rte_mbuf * mbuf;
- struct rte_mbuf ** pkts = (struct rte_mbuf **)objs;
- void ** batch_pkts = objs;
- uint16_t batch_next_node_index = BFD_NEXT_ETH_EGRESS;
- uint64_t drop_for_unexpected_pkts = 0;
- uint64_t drop_for_no_free_session_id = 0;
-
- /* Single Packet Processing */
- while (n_left_from > 0)
- {
- mbuf = pkts[0];
- pkts += 1;
- n_left_from -= 1;
-
- /* Get Private Data */
- uint8_t bfd_state = BFD_STATE_ADMINDOWN;
- uint16_t bfd_session_id = UINT16_MAX;
- int expect_result = RT_ERR;
-
- struct mrb_metadata * mrb_meta = mrbuf_cz_data(mbuf, MR_NODE_CTRLZONE_ID);
- struct pkt_parser_result * pkt_parser_result = &mrb_meta->pkt_parser_result;
-
- static const uint16_t expect_layers[] = {
- LAYER_TYPE_ID_ETHER,
- LAYER_TYPE_ID_IPV4,
- LAYER_TYPE_ID_UDP,
- };
-
- expect_result = complex_layer_type_expect(pkt_parser_result, expect_layers, RTE_DIM(expect_layers));
- if (unlikely(expect_result < 0))
- {
- drop_for_unexpected_pkts++;
- next_node_index = BFD_NEXT_PKT_DROP;
- goto node_enqueue;
- }
-
- struct rte_ether_hdr * ether_hdr = rte_pktmbuf_mtod(mbuf, struct rte_ether_hdr *);
- struct rte_ipv4_hdr * ipv4_hdr =
- complex_layer_jump_to_outermost(pkt_parser_result, rte_pktmbuf_mtod(mbuf, void *), LAYER_TYPE_ID_IPV4);
- struct rte_udp_hdr * udp_hdr =
- complex_layer_jump_to_outermost(pkt_parser_result, rte_pktmbuf_mtod(mbuf, void *), LAYER_TYPE_ID_UDP);
-
- assert(ipv4_hdr != NULL);
- assert(udp_hdr != NULL);
-
- /* Get Session Id */
- bfd_session_id = get_bfd_session_id(graph->id, ipv4_hdr);
- if (unlikely(bfd_session_id == UINT16_MAX))
- {
- drop_for_no_free_session_id++;
- next_node_index = BFD_NEXT_PKT_DROP;
- goto node_enqueue;
- }
-
- /* Reply Bfd Request */
- bfd_state = bfd_reply(ether_hdr, ipv4_hdr, udp_hdr, bfd_session_id);
- stats_per_graph[graph->id].stats_for_session[bfd_session_id].rx_pkts++;
-
- /* Update prev_tsc */
- stats_per_graph[graph->id].stats_for_session[bfd_session_id].prev_tsc = rte_rdtsc();
-
- /* From Ingress Port Id Get Next Node Index (TX) */
- mrb_meta->port_egress = mrb_meta->port_ingress;
- next_node_index = BFD_NEXT_ETH_EGRESS;
-
- node_enqueue:
- /* Check if tracing is enabled for the current Mbuf */
- if (unlikely(dp_trace_record_can_emit(mbuf, DP_TRACE_MEASUREMENT_TYPE_TRACE)))
- {
- gen_store_trace_info(node, mbuf, next_node_index, expect_result, bfd_session_id, bfd_state);
- }
-
- /* Judge The Next Index Whether To Change */
- if (unlikely(batch_next_node_index != next_node_index))
- {
- /* If The Next Index Has Been Changed,Enqueue Last Pkts */
- rte_node_enqueue(graph, node, batch_next_node_index, batch_pkts, last_spec);
- batch_pkts += last_spec;
- last_spec = 1;
- batch_next_node_index = next_node_index;
- }
- else
- {
- /* If The Next Index Not Change, Update The Lasts */
- last_spec++;
- }
- }
-
- /* Update graph stats */
- struct bfd_stats * graph_stats = &stats_per_graph[graph->id];
- graph_stats->total_pkts += cnt;
- graph_stats->pkts_per_batch = cnt;
- graph_stats->drop_for_unexpected_pkts += drop_for_unexpected_pkts;
- graph_stats->drop_for_no_free_session_id += drop_for_no_free_session_id;
-
- /* Process The Remaining Packets */
- if (likely(last_spec > 0))
- rte_node_enqueue(graph, node, batch_next_node_index, batch_pkts, last_spec);
- return cnt;
-}
-
-/* Bfd Node Base */
-static struct rte_node_register bfd_node_base = {
- .process = bfd_node_process,
- .name = "bfd",
- .init = bfd_node_init,
- .nb_edges = BFD_NEXT_MAX,
- .next_nodes =
- {
- [BFD_NEXT_PKT_DROP] = "pkt_drop_trap",
- [BFD_NEXT_ETH_EGRESS] = "eth_egress",
- },
-};
-RTE_NODE_REGISTER(bfd_node_base);
-
-/************************************** Bfd Statistics **************************************/
+/**
+ * @brief Create a JSON object from an array of 64-bit unsigned integers.
+ *
+ * This function creates a JSON object from an array of 64-bit unsigned integers.
+ *
+ * @param array The array of 64-bit unsigned integers.
+ * @param nr_elements The number of elements in the array.
+ * @return Pointer to the created JSON object.
+ */
cJSON * bfd_node_monit_loop(struct sc_main * sc)
{
cJSON * json_root = cJSON_CreateObject();
unsigned int nr_graphs = sc->nr_io_thread;
+ struct bfd_node_main * bfd_node_main = g_bfd_node_main_get();
+
+ assert(nr_graphs == bfd_node_main->nr_graphs);
uint64_t total_pkts[nr_graphs];
uint64_t pkts_per_batch[nr_graphs];
@@ -326,8 +598,10 @@ cJSON * bfd_node_monit_loop(struct sc_main * sc)
for (uint32_t graph_id = 0; graph_id < nr_graphs; graph_id++)
{
- struct bfd_stats * stats = &stats_per_graph[graph_id];
- if (stats->total_pkts == 0)
+ uint64_t values[BFD_METRIC_MAX];
+ sc_metrics_values_get(bfd_node_main->metrics_handles[graph_id], values, BFD_METRIC_MAX);
+
+ if (values[BFD_METRIC_TOT_PKTS] == 0)
{
total_pkts[graph_id] = 0;
pkts_per_batch[graph_id] = 0;
@@ -336,10 +610,10 @@ cJSON * bfd_node_monit_loop(struct sc_main * sc)
continue;
}
- total_pkts[graph_id] = stats->total_pkts;
- pkts_per_batch[graph_id] = stats->pkts_per_batch;
- unexpected_pkts[graph_id] = stats->drop_for_unexpected_pkts;
- no_free_session_id[graph_id] = stats->drop_for_no_free_session_id;
+ total_pkts[graph_id] = values[BFD_METRIC_TOT_PKTS];
+ pkts_per_batch[graph_id] = values[BFD_METRIC_PKTS_BATCH];
+ unexpected_pkts[graph_id] = values[BFD_METRIC_DROP_UNEXP_PKTS];
+ no_free_session_id[graph_id] = values[BFD_METRIC_DROP_NO_FREE_SESS_PKTS];
}
cJSON * json_total_pkts = create_uint64_array(total_pkts, nr_graphs);
@@ -355,41 +629,42 @@ cJSON * bfd_node_monit_loop(struct sc_main * sc)
cJSON_AddItemToObject(json_root, "bfd, drop_for_no_free_session_id", json_no_free_session_id);
uint32_t session_num = 0;
- struct bfd_stats_for_session sessions[MR_BFD_MAX_SESSION_NUM] = {};
+ struct bfd_session sessions[MR_BFD_MAX_SESSION_NUM] = {};
const uint64_t drain_tsc = (rte_get_tsc_hz() + US_PER_S - 1) / US_PER_S * 500000;
for (uint32_t graph_id = 0; graph_id < nr_graphs; ++graph_id)
{
- struct bfd_stats * stats = &stats_per_graph[graph_id];
- if (stats->total_pkts == 0)
+ struct bfd_session_handle * bfd_session_handle = bfd_node_main->bfd_session_handles[graph_id];
+ if (bfd_session_handle->current_sessions == 0)
continue;
- for (int i = 0; i < MR_BFD_MAX_SESSION_NUM; ++i)
+ for (int i = 0; i < bfd_session_handle->max_sessions; ++i)
{
- struct bfd_stats_for_session * current_session = &stats->stats_for_session[i];
- if (current_session->ip_addr == 0)
+ struct bfd_session * session = &bfd_session_handle->bfd_sessions[i];
+ if (session->ipv4_address == 0)
continue;
bool should_add = true;
for (int j = 0; j < session_num; ++j)
{
- if (sessions[j].ip_addr != current_session->ip_addr)
+ if (sessions[j].ipv4_address != session->ipv4_address)
continue;
should_add = false;
- if (current_session->prev_tsc > sessions[j].prev_tsc)
+
+ /* Update the session tsc */
+ if (session->prev_tsc > sessions[j].prev_tsc)
{
- sessions[j].rx_pkts += current_session->rx_pkts;
- sessions[j].prev_tsc = current_session->prev_tsc;
+ sessions[j].prev_tsc = session->prev_tsc;
}
+
break;
}
- if (should_add && session_num < MR_BFD_MAX_SESSION_NUM)
+ if (should_add && session_num < bfd_session_handle->max_sessions)
{
- sessions[session_num].rx_pkts = current_session->rx_pkts;
- sessions[session_num].ip_addr = current_session->ip_addr;
- sessions[session_num].prev_tsc = current_session->prev_tsc;
+ sessions[session_num].ipv4_address = session->ipv4_address;
+ sessions[session_num].prev_tsc = session->prev_tsc;
++session_num;
}
}
@@ -398,11 +673,10 @@ cJSON * bfd_node_monit_loop(struct sc_main * sc)
for (int i = 0; i < session_num; ++i)
{
struct in_addr ip_addr;
- ip_addr.s_addr = sessions[i].ip_addr;
+ ip_addr.s_addr = sessions[i].ipv4_address;
cJSON * session_obj = cJSON_CreateObject();
cJSON_AddStringToObject(session_obj, "ip_addr", inet_ntoa(ip_addr));
- cJSON_AddNumberToObject(session_obj, "rx_pkts", sessions[i].rx_pkts);
uint64_t prev_tsc = sessions[i].prev_tsc;
uint64_t cur_tsc = rte_rdtsc();
@@ -420,3 +694,135 @@ cJSON * bfd_node_monit_loop(struct sc_main * sc)
cJSON_AddNumberToObject(json_root, "total_session_num", session_num);
return json_root;
}
+
+/************************************************** BFD node process **************************************************/
+/**
+ * @brief Process the BFD node.
+ *
+ * This function processes the BFD node by handling incoming BFD packets and replying to them.
+ *
+ * @param graph Pointer to the graph.
+ * @param node Pointer to the node.
+ * @param objs Array of pointers to objects.
+ * @param cnt The number of objects in the array.
+ * @return The number of objects processed.
+ */
+static uint16_t bfd_node_process(struct rte_graph * graph, struct rte_node * node, void ** objs, uint16_t cnt)
+{
+ uint16_t last_spec = 0;
+ uint16_t n_left_from = cnt;
+ uint16_t batch_next_node_index = BFD_NEXT_ETH_EGRESS;
+ struct rte_mbuf ** pkts = (struct rte_mbuf **)objs;
+ void ** batch_pkts = objs;
+
+ uint64_t inc_values[BFD_METRIC_MAX] = {0};
+
+ struct bfd_session_handle * bfd_session_handle = g_bfd_main->bfd_session_handles[graph->id];
+
+ /* Single packet processing */
+ while (n_left_from > 0)
+ {
+ struct rte_mbuf * mbuf = pkts[0];
+ pkts += 1;
+ n_left_from -= 1;
+
+ struct mrb_metadata * mrb_meta = mrbuf_cz_data(mbuf, MR_NODE_CTRLZONE_ID);
+ struct pkt_parser_result * pkt_parser_result = &mrb_meta->pkt_parser_result;
+
+ static const uint16_t expect_layers[] = {
+ LAYER_TYPE_ID_ETHER,
+ LAYER_TYPE_ID_IPV4,
+ LAYER_TYPE_ID_UDP,
+ };
+
+ uint16_t next_node_index = BFD_NEXT_ETH_EGRESS;
+ int expect_result = complex_layer_type_expect(pkt_parser_result, expect_layers, RTE_DIM(expect_layers));
+ if (unlikely(expect_result < 0))
+ {
+ inc_values[BFD_METRIC_DROP_UNEXP_PKTS]++;
+ next_node_index = BFD_NEXT_PKT_DROP;
+ goto node_enqueue;
+ }
+
+ struct rte_ether_hdr * ether_hdr = rte_pktmbuf_mtod(mbuf, struct rte_ether_hdr *);
+ struct rte_ipv4_hdr * ipv4_hdr = complex_layer_jump_to_outermost(
+ pkt_parser_result, rte_pktmbuf_mtod(mbuf, void *), LAYER_TYPE_ID_IPV4);
+ struct rte_udp_hdr * udp_hdr = complex_layer_jump_to_outermost(
+ pkt_parser_result, rte_pktmbuf_mtod(mbuf, void *), LAYER_TYPE_ID_UDP);
+ struct bfd_hdr * bfd_hdr = (struct bfd_hdr *)(udp_hdr + 1);
+
+ assert(ipv4_hdr != NULL);
+ assert(udp_hdr != NULL);
+ assert(bfd_hdr != NULL);
+
+ /* Bfd session search */
+ struct bfd_session * bfd_session = bfd_session_lookup(bfd_session_handle, ipv4_hdr->src_addr);
+
+ if (unlikely(bfd_session == NULL))
+ {
+ bfd_session = bfd_session_add(bfd_session_handle, ipv4_hdr->src_addr);
+ if (unlikely(bfd_session == NULL))
+ {
+ inc_values[BFD_METRIC_DROP_NO_FREE_SESS_PKTS]++;
+ next_node_index = BFD_NEXT_PKT_DROP;
+ goto node_enqueue;
+ }
+ }
+
+ /* Reply bfd request */
+ uint8_t bfd_state = bfd_pkt_reply(ether_hdr, ipv4_hdr, udp_hdr, bfd_hdr, bfd_session);
+
+ /* Update bfd session prev_tsc */
+ __bfd_session_update(bfd_session);
+
+ /* From ingress port id get next node index (TX) */
+ mrb_meta->port_egress = mrb_meta->port_ingress;
+ next_node_index = BFD_NEXT_ETH_EGRESS;
+
+ node_enqueue:
+ /* Check if tracing is enabled for the current mbuf */
+ if (unlikely(dp_trace_record_can_emit(mbuf, DP_TRACE_MEASUREMENT_TYPE_TRACE)))
+ {
+ gen_store_trace_info(node, mbuf, next_node_index, expect_result, bfd_state, bfd_session);
+ }
+
+ /* Judge the next index whether to change */
+ if (unlikely(batch_next_node_index != next_node_index))
+ {
+ /* If the next index has been changed, enqueue last pkts */
+ rte_node_enqueue(graph, node, batch_next_node_index, batch_pkts, last_spec);
+ batch_pkts += last_spec;
+ last_spec = 1;
+ batch_next_node_index = next_node_index;
+ }
+ else
+ {
+ /* If the next index not change, update the lasts */
+ last_spec++;
+ }
+ }
+
+ /* Update metrics */
+ inc_values[BFD_METRIC_TOT_PKTS] += cnt;
+ sc_metrics_accumulate(g_bfd_main->metrics_handles[graph->id], inc_values, RTE_DIM(inc_values));
+ sc_metrics_value_set(g_bfd_main->metrics_handles[graph->id], cnt, BFD_METRIC_PKTS_BATCH);
+
+ /* Process the remaining packets */
+ if (likely(last_spec > 0))
+ rte_node_enqueue(graph, node, batch_next_node_index, batch_pkts, last_spec);
+ return cnt;
+}
+
+/* Bfd node base */
+static struct rte_node_register bfd_node_base = {
+ .process = bfd_node_process,
+ .name = "bfd",
+ .init = NULL,
+ .nb_edges = BFD_NEXT_MAX,
+ .next_nodes =
+ {
+ [BFD_NEXT_PKT_DROP] = "pkt_drop_trap",
+ [BFD_NEXT_ETH_EGRESS] = "eth_egress",
+ },
+};
+RTE_NODE_REGISTER(bfd_node_base);
diff --git a/service/src/sc_metrics.c b/service/src/sc_metrics.c
new file mode 100644
index 0000000..72dc9ea
--- /dev/null
+++ b/service/src/sc_metrics.c
@@ -0,0 +1,221 @@
+#include <rte_memcpy.h>
+
+#include <sc_metrics.h>
+
+/**
+ * @brief Round up a value to the nearest multiple of 8.
+ * Note: This function is not thread-safe.
+ *
+ * @param value The value to round up.
+ * @return The rounded up value.
+ */
+uint16_t round_up_to_multiple_of_8(uint16_t value)
+{
+ return (value + 7) & ~7;
+}
+
+/**
+ * @brief Create and initialize a new SC metrics handle.
+ *
+ * This function allocates and initializes a new SC metrics handle with the
+ * specified capacity. It ensures that the capacity is rounded up to the
+ * nearest multiple of 8. The handle includes memory for storing metrics
+ * values based on the rounded capacity.
+ *
+ * @param capacity The number of metrics values that the handle should be able
+ * to store. This value is rounded up to the nearest multiple
+ * of 8.
+ *
+ * @return Pointer to the newly created SC metrics handle, or NULL if memory
+ * allocation fails or if an invalid capacity is provided (0).
+ */
+struct sc_metrics_handle * sc_metrics_handle_create(uint16_t capacity)
+{
+ if (capacity == 0)
+ {
+ MR_ERROR("Error allocating stats handle: invalid input parameters.");
+ return NULL;
+ }
+ else if (capacity > SC_METRICS_MAX_CAPACITY)
+ {
+ MR_ERROR("Error allocating stats handle: capacity exceeds maximum limit.");
+ return NULL;
+ }
+
+ struct sc_metrics_handle * metrics_handle = (struct sc_metrics_handle *)ZMALLOC(sizeof(struct sc_metrics_handle));
+ MR_VERIFY_MALLOC(metrics_handle);
+
+ metrics_handle->capacity = round_up_to_multiple_of_8(capacity);
+
+ metrics_handle->values = (volatile uint64_t *)ZMALLOC(sizeof(uint64_t) * metrics_handle->capacity);
+ MR_VERIFY_MALLOC(metrics_handle->values);
+
+ return metrics_handle;
+}
+
+/**
+ * @brief Delete and free an SC metrics handle.
+ *
+ * This function deallocates the memory associated with an SC metrics handle,
+ * including its values array. After freeing the memory, it sets the pointers
+ * to NULL to avoid dangling pointers.
+ *
+ * @param metrics_handle Pointer to the SC metrics handle to be deleted.
+ * If this pointer is NULL, no action is taken.
+ *
+ * @return Return value indicating success or failure:
+ * - RT_SUCCESS (0) if the handle is successfully deleted.
+ * - RT_ERR (-1) if the handle is NULL and cannot be deleted.
+ */
+int sc_metrics_handle_delete(struct sc_metrics_handle * metrics_handle)
+{
+ if (metrics_handle == NULL)
+ {
+ MR_ERROR("Error deleting stats handle: invalid input parameters.");
+ return RT_ERR;
+ }
+
+ FREE((void *)metrics_handle->values);
+ metrics_handle->values = NULL;
+
+ FREE((void *)metrics_handle);
+
+ return RT_SUCCESS;
+}
+
+/**
+ * @brief Set the values for the SC metrics handle.
+ *
+ * This function copies an array of metric values into the SC metrics handle.
+ * It ensures that the provided capacity does not exceed the handle's allocated
+ * capacity. The function uses `rte_memcpy` to perform the copy operation.
+ *
+ * @param metrics_handle Pointer to the SC metrics handle where the values will
+ * be set. This handle must be valid and initialized.
+ * @param stats Array of metric values to be copied into the handle.
+ * @param capacity Number of metric values to copy from the `stats` array.
+ * This should not exceed the handle's allocated capacity.
+ *
+ * @return Return value indicating success:
+ * - RT_SUCCESS (0) if the values are successfully copied.
+ */
+int sc_metrics_values_set(struct sc_metrics_handle * metrics_handle, uint64_t values[], uint16_t capacity)
+{
+ assert(metrics_handle != NULL);
+ assert(metrics_handle->values != NULL);
+ assert(capacity <= metrics_handle->capacity);
+
+ rte_memcpy((void *)metrics_handle->values, values, sizeof(uint64_t) * capacity);
+ return RT_SUCCESS;
+}
+
+/**
+ * @brief Get the values from the SC metrics handle.
+ *
+ * This function copies the metric values from the SC metrics handle into the
+ * provided array. It ensures that the provided capacity does not exceed the
+ * handle's allocated capacity. The function uses `rte_memcpy` to perform the
+ * copy operation.
+ *
+ * @param metrics_handle Pointer to the SC metrics handle from which the values
+ * will be retrieved. This handle must be valid and initialized.
+ * @param values Array where the metric values will be copied.
+ * @param capacity Number of metric values to copy into the `values` array.
+ * This should not exceed the handle's allocated capacity.
+ *
+ * @return Return value indicating success:
+ * - RT_SUCCESS (0) if the values are successfully copied.
+ */
+int sc_metrics_values_get(struct sc_metrics_handle * metrics_handle, uint64_t values[], uint16_t capacity)
+{
+ assert(metrics_handle != NULL);
+ assert(metrics_handle->values != NULL);
+ assert(capacity <= metrics_handle->capacity);
+
+ rte_memcpy(values, (void *)metrics_handle->values, sizeof(uint64_t) * capacity);
+ return RT_SUCCESS;
+}
+
+/**
+ * @brief Get a single value from the SC metrics handle.
+ *
+ * This function retrieves a single metric value from the SC metrics handle at
+ * the specified key. The function ensures that the key is within the handle's
+ * allocated capacity. The value is directly read from the handle's values array
+ * at the specified key.
+ *
+ * @param metrics_handle Pointer to the SC metrics handle from which the value
+ * will be retrieved. This handle must be valid and initialized.
+ * @param value Pointer to the variable where the metric value will be stored.
+ * @param key The key from which to retrieve the metric value. This key must be
+ * within the handle's allocated capacity.
+ *
+ * @return Return value indicating success:
+ * - RT_SUCCESS (0) if the value is successfully retrieved.
+ */
+int sc_metrics_value_get(struct sc_metrics_handle * metrics_handle, uint64_t * value, uint16_t key)
+{
+ assert(metrics_handle != NULL);
+ assert(metrics_handle->values != NULL);
+ assert(key < metrics_handle->capacity);
+
+ *value = metrics_handle->values[key];
+ return RT_SUCCESS;
+}
+
+/**
+ * @brief Reset all values in the SC metrics handle to zero.
+ *
+ * This function sets all values in the SC metrics handle to zero. It uses
+ * `memset` to overwrite the entire allocated space for metric values with zeros.
+ * This operation effectively resets the metrics handle to its initial state.
+ *
+ * @param metrics_handle Pointer to the SC metrics handle to be reset.
+ * This handle must be valid and initialized.
+ *
+ * @return Return value indicating success:
+ * - RT_SUCCESS (0) if the values are successfully reset.
+ */
+int sc_metrics_values_reset(struct sc_metrics_handle * metrics_handle)
+{
+ assert(metrics_handle != NULL);
+ assert(metrics_handle->values != NULL);
+
+ memset((void *)metrics_handle->values, 0, sizeof(uint64_t) * metrics_handle->capacity);
+ return RT_SUCCESS;
+}
+
+/**
+ * @brief Accumulate values into the SC metrics handle.
+ *
+ * This function adds values from the `inc_values` array to the existing values
+ * in the SC metrics handle. The accumulation is performed element-wise for
+ * the specified `capacity`. The function ensures that the provided capacity
+ * does not exceed the handle's allocated capacity.
+ *
+ * @param metrics_handle Pointer to the SC metrics handle where the values
+ * will be accumulated. This handle must be valid and
+ * initialized.
+ * @param inc_values Array of values to be added to the current values in
+ * the handle.
+ * @param capacity Number of values to accumulate. This should not exceed
+ * the handle's allocated capacity.
+ *
+ * @return Return value indicating success:
+ * - RT_SUCCESS (0) if the values are successfully accumulated.
+ */
+int sc_metrics_accumulate(struct sc_metrics_handle * metrics_handle, uint64_t inc_values[], uint16_t capacity)
+{
+ assert(metrics_handle != NULL);
+ assert(metrics_handle->values != NULL);
+ assert(capacity <= metrics_handle->capacity);
+
+ volatile uint64_t * values = metrics_handle->values;
+
+ for (uint16_t i = 0; i < capacity; i++)
+ {
+ values[i] += inc_values[i];
+ }
+
+ return RT_SUCCESS;
+}
diff --git a/service/test/test_common.c b/service/test/test_common.c
new file mode 100644
index 0000000..4d9f20d
--- /dev/null
+++ b/service/test/test_common.c
@@ -0,0 +1,167 @@
+#include <rte_ether.h>
+#include <rte_ip.h>
+#include <rte_mbuf.h>
+#include <rte_udp.h>
+
+#include <sc_common.h>
+
+/**
+ * @brief Construct an Ethernet header
+ *
+ * @param eth_hdr Pointer to the Ethernet header
+ * @param src_mac Source MAC address
+ * @param dst_mac Destination MAC address
+ * @param eth_type Ethernet type
+ */
+void eth_header_construct(struct rte_ether_hdr * eth_hdr, struct rte_ether_addr * src_mac,
+ struct rte_ether_addr * dst_mac, uint16_t eth_type)
+{
+ const struct rte_ether_addr default_src_mac = {.addr_bytes = {0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}};
+ const struct rte_ether_addr default_dst_mac = {.addr_bytes = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66}};
+
+ if (src_mac == NULL)
+ {
+ rte_memcpy(&eth_hdr->src_addr, &default_src_mac, sizeof(struct rte_ether_addr));
+ }
+ else
+ {
+ rte_memcpy(&eth_hdr->src_addr, src_mac, sizeof(struct rte_ether_addr));
+ }
+
+ if (dst_mac == NULL)
+ {
+ rte_memcpy(&eth_hdr->dst_addr, &default_dst_mac, sizeof(struct rte_ether_addr));
+ }
+ else
+ {
+ rte_memcpy(&eth_hdr->dst_addr, dst_mac, sizeof(struct rte_ether_addr));
+ }
+
+ eth_hdr->ether_type = rte_cpu_to_be_16(eth_type);
+}
+
+/**
+ * @brief Construct an IPv4 header
+ *
+ * @param ipv4_hdr Pointer to the IPv4 header
+ * @param src_ip Source IP address
+ * @param dst_ip Destination IP address
+ * @param proto Protocol
+ * @return int 0 on success, -1 on failure
+ */
+int ipv4_header_construct(struct rte_ipv4_hdr * ipv4_hdr, const char * src_ip, const char * dst_ip, uint8_t proto)
+{
+ const char * default_src_ip = "192.168.1.1";
+ const char * default_dst_ip = "192.168.1.2";
+
+ if (src_ip == NULL)
+ {
+ src_ip = default_src_ip;
+ }
+
+ if (dst_ip == NULL)
+ {
+ dst_ip = default_dst_ip;
+ }
+
+ memset(ipv4_hdr, 0, sizeof(struct rte_ipv4_hdr));
+
+ ipv4_hdr->version_ihl = (4 << 4) | (sizeof(struct rte_ipv4_hdr) / 4);
+ ipv4_hdr->type_of_service = 0;
+ ipv4_hdr->total_length = rte_cpu_to_be_16(sizeof(struct rte_ipv4_hdr));
+ ipv4_hdr->packet_id = 0;
+ ipv4_hdr->fragment_offset = rte_cpu_to_be_16(0x4000); // Don't fragment flag
+ ipv4_hdr->time_to_live = 64;
+ ipv4_hdr->next_proto_id = proto;
+ ipv4_hdr->hdr_checksum = 0;
+
+ if (inet_pton(AF_INET, src_ip, &ipv4_hdr->src_addr) != 1)
+ {
+ return -1;
+ }
+
+ if (inet_pton(AF_INET, dst_ip, &ipv4_hdr->dst_addr) != 1)
+ {
+ return -1;
+ }
+
+ ipv4_hdr->hdr_checksum = rte_ipv4_cksum(ipv4_hdr);
+ return 0;
+}
+
+/**
+ * @brief Construct a UDP header
+ *
+ * @param udp_hdr Pointer to the UDP header
+ * @param src_port Source port
+ * @param dst_port Destination port
+ * @param length Length of the UDP payload
+ */
+void udp_header_construct(struct rte_udp_hdr * udp_hdr, uint16_t src_port, uint16_t dst_port, uint16_t length)
+{
+ udp_hdr->src_port = rte_cpu_to_be_16(src_port);
+ udp_hdr->dst_port = rte_cpu_to_be_16(dst_port);
+ udp_hdr->dgram_len = sizeof(struct rte_udp_hdr) + length;
+ udp_hdr->dgram_cksum = 0;
+}
+
+/**
+ * @brief Construct a BFD header
+ *
+ * @param bfd_hdr Pointer to the BFD header
+ * @param my_discriminator My discriminator
+ * @param your_discriminator Your discriminator
+ * @param state BFD state
+ */
+void bfd_header_construct(struct bfd_hdr * bfd_hdr, uint32_t * my_discriminator, uint32_t * your_discriminator,
+ uint8_t state)
+{
+ memset(bfd_hdr, 0, sizeof(struct bfd_hdr));
+
+ bfd_hdr->version_diag = 1;
+ bfd_hdr->flags = 0;
+ bfd_hdr->state = state;
+
+ uint32_t discriminator = 0;
+ if (my_discriminator != NULL)
+ {
+ discriminator = rte_be_to_cpu_32(*my_discriminator);
+ rte_memcpy(bfd_hdr->my_discriminator, &discriminator, 4);
+ }
+
+ if (your_discriminator != NULL)
+ {
+ discriminator = rte_be_to_cpu_32(*your_discriminator);
+ rte_memcpy(bfd_hdr->your_discriminator, your_discriminator, 4);
+ }
+}
+
+/**
+ * @brief Creates a configuration file with the specified content.
+ *
+ * This function removes any existing file with the name "cmocka_test_config.ini",
+ * creates a new file with the same name, writes the provided content into the file,
+ * and then closes the file. If the file cannot be opened for writing, the function
+ * returns NULL.
+ *
+ * @param content A string containing the content to be written to the configuration file.
+ *
+ * @return The name of the created configuration file ("cmocka_test_config.ini") on success,
+ * or NULL if the file could not be created or written.
+ */
+const char * create_config_file(const char * content)
+{
+ const char * filename = "cmocka_test_config.ini";
+
+ remove(filename);
+
+ FILE * fp_cfgfile = fopen(filename, "w");
+ if (fp_cfgfile == NULL)
+ {
+ return NULL;
+ }
+
+ fprintf(fp_cfgfile, "%s", content);
+ fclose(fp_cfgfile);
+ return filename;
+}
diff --git a/service/test/test_node_bfd.c b/service/test/test_node_bfd.c
new file mode 100644
index 0000000..d428a00
--- /dev/null
+++ b/service/test/test_node_bfd.c
@@ -0,0 +1,970 @@
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <cmocka.h>
+
+#include <rte_eal.h>
+#include <rte_graph.h>
+#include <rte_graph_worker.h>
+#include <rte_mempool.h>
+
+#include <sc_node.h>
+#include <sc_node_common.h>
+
+struct bfd_node_main;
+struct bfd_session;
+struct bfd_session_handle;
+
+struct bfd_test_main
+{
+ struct sc_main * mock_sc;
+ rte_graph_t graph;
+ struct bfd_node_main * bfd_node_main;
+};
+
+extern int bfd_init(struct sc_main * sc);
+extern void bfd_deinit(struct sc_main * sc);
+
+extern int bfd_node_main_delete(struct bfd_node_main * bfd_main);
+extern struct bfd_node_main * bfd_node_main_create(uint16_t nr_graphs);
+extern struct bfd_node_main * g_bfd_node_main_get(void);
+extern int g_bfd_node_main_set(struct bfd_node_main * bfd_node_main);
+
+extern struct bfd_session_handle * bfd_session_handle_create(uint16_t max_sessions);
+extern int bfd_session_handle_delete(struct bfd_session_handle * bfd_session_handle);
+extern struct bfd_session_handle * bfd_session_handle_get(struct bfd_node_main * bfd_main, uint16_t graph_id);
+extern int bfd_session_handle_is_empty(struct bfd_session_handle * bfd_session_handle);
+extern int bfd_session_handle_is_full(struct bfd_session_handle * bfd_session_handle);
+extern struct bfd_session * bfd_session_lookup(struct bfd_session_handle * bfd_session_handle, uint32_t ipv4_address);
+extern struct bfd_session * bfd_session_add(struct bfd_session_handle * bfd_session_handle, uint32_t ipv4_address);
+extern int bfd_session_update(struct bfd_session * bfd_session);
+extern int bfd_session_delete(struct bfd_session_handle * bfd_session_handle, uint32_t ipv4_address);
+
+extern void eth_header_construct(struct rte_ether_hdr * eth_hdr, struct rte_ether_addr * src_mac,
+ struct rte_ether_addr * dst_mac, uint16_t eth_type);
+extern int ipv4_header_construct(struct rte_ipv4_hdr * ipv4_hdr, const char * src_ip, const char * dst_ip,
+ uint8_t proto);
+extern void udp_header_construct(struct rte_udp_hdr * udp_hdr, uint16_t src_port, uint16_t dst_port, uint16_t length);
+extern void bfd_header_construct(struct bfd_hdr * bfd_hdr, uint32_t * my_discriminator, uint32_t * your_discriminator,
+ uint8_t state);
+extern const char * create_config_file(const char * content);
+
+static struct rte_mempool * mock_pktmbuf_pool = NULL;
+
+/* ------------------------------------------- Test group setup/teardown ------------------------------------------- */
+static int testgroup_bfd_init_setup(void ** state)
+{
+ struct sc_main * mock_sc = rte_zmalloc(NULL, sizeof(struct sc_main), 0);
+ assert_non_null(mock_sc);
+
+ const char * test_cfgfile_content = "[bfd]\n"
+ "disc_method=0";
+ const char * filename = create_config_file(test_cfgfile_content);
+ assert_non_null(filename);
+
+ strcpy(mock_sc->local_cfgfile, filename);
+
+ mock_sc->nr_io_thread = 1;
+ *state = mock_sc;
+ return 0;
+}
+
+static int testgroup_bfd_init_teardown(void ** state)
+{
+ struct sc_main * mock_sc = *state;
+ rte_free(mock_sc);
+ return 0;
+}
+
+static int testgroup_bfd_discriminator_method_rfc_setup(void ** state)
+{
+ /* mock pkt source to generate mock packets */
+ const char * node_patterns[] = {
+ "mock_pkt_input",
+ "mock_pkt_output",
+ "bfd",
+ };
+
+ /* create a fake graph */
+ struct rte_graph_param graph_param = {
+ .node_patterns = node_patterns,
+ .nb_node_patterns = RTE_DIM(node_patterns),
+ .socket_id = 0,
+ };
+
+ /* update the pkt input's next id */
+ rte_node_t node_id_pkt_input = rte_node_from_name("mock_pkt_input");
+ assert_int_not_equal(node_id_pkt_input, RTE_NODE_ID_INVALID);
+
+ const char * updated_next_node[] = {"bfd"};
+ int rc = rte_node_edge_update(node_id_pkt_input, 0, updated_next_node, RTE_DIM(updated_next_node));
+ assert_int_equal(rc, 1);
+
+ /* update the node edge */
+ rte_node_t node_id_bfd = rte_node_from_name("bfd");
+ assert_int_not_equal(node_id_bfd, RTE_NODE_ID_INVALID);
+
+ const char * bfd_updated_next_node[] = {
+ "mock_pkt_drop",
+ "mock_pkt_output",
+ };
+
+ rc = rte_node_edge_update(node_id_bfd, 0, bfd_updated_next_node, RTE_DIM(bfd_updated_next_node));
+
+ assert_int_equal(rc, 2);
+
+ rte_graph_t graph = rte_graph_create("test_bfd", &graph_param);
+ assert_int_not_equal(graph, RTE_GRAPH_ID_INVALID);
+
+ struct sc_main * mock_sc = rte_zmalloc(NULL, sizeof(struct sc_main), 0);
+ assert_non_null(mock_sc);
+
+ mock_sc->nr_io_thread = 1;
+
+ const char * test_cfgfile_content = "[bfd]\n"
+ "disc_method=0";
+ const char * filename = create_config_file(test_cfgfile_content);
+ assert_non_null(filename);
+
+ strcpy(mock_sc->local_cfgfile, filename);
+
+ int ret = bfd_init(mock_sc);
+ assert_int_equal(ret, RT_SUCCESS);
+
+ struct bfd_node_main * bfd_node_main = g_bfd_node_main_get();
+ assert_non_null(bfd_node_main);
+
+ struct bfd_test_main * bfd_test_main = rte_zmalloc(NULL, sizeof(struct bfd_test_main), 0);
+ assert_non_null(bfd_test_main);
+
+ bfd_test_main->mock_sc = mock_sc;
+ bfd_test_main->graph = graph;
+ bfd_test_main->bfd_node_main = bfd_node_main;
+
+ *state = bfd_test_main;
+ return 0;
+}
+
+static int testgroup_bfd_discriminator_method_rfc_teardown(void ** state)
+{
+ struct bfd_test_main * bfd_test_main = *state;
+
+ /* deinitialize the bfd */
+ bfd_deinit(bfd_test_main->mock_sc);
+ assert_ptr_equal(g_bfd_node_main_get(), NULL);
+
+ /* free the mock_sc */
+ rte_free(bfd_test_main->mock_sc);
+
+ /* destroy the graph */
+ return rte_graph_destroy(bfd_test_main->graph);
+}
+
+static int testgroup_bfd_discriminator_method_flipping_setup(void ** state)
+{
+ /* mock pkt source to generate mock packets */
+ const char * node_patterns[] = {
+ "mock_pkt_input",
+ "mock_pkt_output",
+ "bfd",
+ };
+
+ /* create a fake graph */
+ struct rte_graph_param graph_param = {
+ .node_patterns = node_patterns,
+ .nb_node_patterns = RTE_DIM(node_patterns),
+ .socket_id = 0,
+ };
+
+ /* update the pkt input's next id */
+ rte_node_t node_id_pkt_input = rte_node_from_name("mock_pkt_input");
+ assert_int_not_equal(node_id_pkt_input, RTE_NODE_ID_INVALID);
+
+ const char * updated_next_node[] = {"bfd"};
+ int rc = rte_node_edge_update(node_id_pkt_input, 0, updated_next_node, RTE_DIM(updated_next_node));
+ assert_int_equal(rc, 1);
+
+ /* update the node edge */
+ rte_node_t node_id_bfd = rte_node_from_name("bfd");
+ assert_int_not_equal(node_id_bfd, RTE_NODE_ID_INVALID);
+
+ const char * bfd_updated_next_node[] = {
+ "mock_pkt_drop",
+ "mock_pkt_output",
+ };
+
+ rc = rte_node_edge_update(node_id_bfd, 0, bfd_updated_next_node, RTE_DIM(bfd_updated_next_node));
+
+ assert_int_equal(rc, 2);
+
+ rte_graph_t graph = rte_graph_create("test_bfd", &graph_param);
+ assert_int_not_equal(graph, RTE_GRAPH_ID_INVALID);
+
+ struct sc_main * mock_sc = rte_zmalloc(NULL, sizeof(struct sc_main), 0);
+ assert_non_null(mock_sc);
+
+ mock_sc->nr_io_thread = 1;
+
+ const char * test_cfgfile_content = "[bfd]\n"
+ "disc_method=1";
+ const char * filename = create_config_file(test_cfgfile_content);
+ assert_non_null(filename);
+
+ strcpy(mock_sc->local_cfgfile, filename);
+
+ int ret = bfd_init(mock_sc);
+ assert_int_equal(ret, RT_SUCCESS);
+
+ struct bfd_node_main * bfd_node_main = g_bfd_node_main_get();
+ assert_non_null(bfd_node_main);
+
+ struct bfd_test_main * bfd_test_main = rte_zmalloc(NULL, sizeof(struct bfd_test_main), 0);
+ assert_non_null(bfd_test_main);
+
+ bfd_test_main->mock_sc = mock_sc;
+ bfd_test_main->graph = graph;
+ bfd_test_main->bfd_node_main = bfd_node_main;
+
+ *state = bfd_test_main;
+ return 0;
+}
+
+static int testgroup_bfd_discriminator_method_flipping_teardown(void ** state)
+{
+ struct bfd_test_main * bfd_test_main = *state;
+
+ /* deinitialize the bfd */
+ bfd_deinit(bfd_test_main->mock_sc);
+ assert_ptr_equal(g_bfd_node_main_get(), NULL);
+
+ /* free the mock_sc */
+ rte_free(bfd_test_main->mock_sc);
+
+ /* destroy the graph */
+ return rte_graph_destroy(bfd_test_main->graph);
+}
+
+/* --------------------------------------------------- Test case --------------------------------------------------- */
+/* Test Case: BFD initialization success */
+static void testcase_bfd_init_success(void ** state)
+{
+ struct sc_main * mock_sc = *state;
+
+ int rc = bfd_init(mock_sc);
+ assert_int_equal(rc, RT_SUCCESS);
+}
+
+/* Test Case: BFD Deinitialize success */
+static void testcase_bfd_deinit_success(void ** state)
+{
+ struct sc_main * mock_sc = *state;
+
+ bfd_deinit(mock_sc);
+ assert_ptr_equal(g_bfd_node_main_get(), NULL);
+}
+
+/* Test Case: BFD node main API in a successful scenario */
+static void testcase_bfd_node_main_api_success(void ** state)
+{
+ struct bfd_node_main * bfd_node_main = bfd_node_main_create(RTE_MAX_LCORE);
+ assert_non_null(bfd_node_main);
+
+ g_bfd_node_main_set(bfd_node_main);
+ struct bfd_node_main * bfd_node_main_get = g_bfd_node_main_get();
+ assert_non_null(bfd_node_main_get);
+
+ int ret = bfd_node_main_delete(bfd_node_main);
+ assert_int_equal(ret, RT_SUCCESS);
+
+ g_bfd_node_main_set(NULL);
+ bfd_node_main_get = g_bfd_node_main_get();
+ assert_ptr_equal(bfd_node_main_get, NULL);
+}
+
+/* Test Case: BFD node main API in a failure scenario */
+static void testcase_bfd_node_main_api_failure(void ** state)
+{
+ struct bfd_node_main * bfd_node_main = bfd_node_main_create(0);
+ assert_null(bfd_node_main);
+
+ int ret = bfd_node_main_delete(bfd_node_main);
+ assert_int_equal(ret, RT_ERR);
+
+ ret = g_bfd_node_main_set(bfd_node_main);
+ assert_int_equal(ret, RT_SUCCESS);
+
+ struct bfd_node_main * bfd_node_main_get = g_bfd_node_main_get();
+ assert_ptr_equal(bfd_node_main_get, NULL);
+}
+
+/* Test Case: BFD session handle API in a successful scenario */
+static void testcase_bfd_session_handle_api_success(void ** state)
+{
+ /* create the bfd session handle */
+ struct bfd_session_handle * bfd_session_handle = bfd_session_handle_create(UINT16_MAX);
+ assert_non_null(bfd_session_handle);
+
+ /* check the session handle is empty */
+ int ret = bfd_session_handle_is_empty(bfd_session_handle);
+ assert_int_equal(ret, 1);
+
+ /* add the bfd session */
+ for (uint32_t i = 0; i < UINT16_MAX; i++)
+ {
+ struct bfd_session * bfd_session = bfd_session_add(bfd_session_handle, i);
+ assert_non_null(bfd_session);
+ }
+
+ /* check the session handle is full */
+ ret = bfd_session_handle_is_empty(bfd_session_handle);
+ assert_int_equal(ret, 0);
+
+ ret = bfd_session_handle_is_full(bfd_session_handle);
+ assert_int_equal(ret, 1);
+
+ /* lookup and update the bfd session */
+ for (uint32_t i = 0; i < UINT16_MAX; i++)
+ {
+ struct bfd_session * bfd_session = bfd_session_lookup(bfd_session_handle, i);
+ assert_non_null(bfd_session);
+
+ ret = bfd_session_update(bfd_session);
+ assert_int_equal(ret, RT_SUCCESS);
+ }
+
+ /* delete the bfd session */
+ for (uint32_t i = 0; i < UINT16_MAX; i++)
+ {
+ ret = bfd_session_delete(bfd_session_handle, i);
+ assert_int_equal(ret, RT_SUCCESS);
+ }
+
+ /* check the session handle is empty */
+ ret = bfd_session_handle_is_empty(bfd_session_handle);
+ assert_int_equal(ret, 1);
+
+ /* delete the bfd session handle */
+ ret = bfd_session_handle_delete(bfd_session_handle);
+ assert_int_equal(ret, RT_SUCCESS);
+}
+
+/* Test Case: BFD session handle API in a failure scenario */
+static void testcase_bfd_session_handle_api_failure(void ** state)
+{
+ /* create the bfd session handle */
+ struct bfd_session_handle * bfd_session_handle = bfd_session_handle_create(0);
+ assert_null(bfd_session_handle);
+
+ /* delete the bfd session handle */
+ int ret = bfd_session_handle_delete(bfd_session_handle);
+ assert_int_equal(ret, RT_ERR);
+
+ bfd_session_handle = bfd_session_handle_create(1);
+ assert_non_null(bfd_session_handle);
+
+ /* add the bfd session */
+ struct bfd_session * bfd_session = bfd_session_add(bfd_session_handle, 1);
+ assert_non_null(bfd_session);
+
+ /* add the bfd session again */
+ bfd_session = bfd_session_add(bfd_session_handle, 2);
+ assert_null(bfd_session);
+
+ /* delete the bfd session */
+ ret = bfd_session_delete(bfd_session_handle, 2);
+ assert_int_equal(ret, RT_ERR);
+
+ /* delete the bfd session again */
+ ret = bfd_session_delete(bfd_session_handle, 1);
+ assert_int_equal(ret, RT_SUCCESS);
+
+ /* lookup the bfd session */
+ bfd_session = bfd_session_lookup(bfd_session_handle, 1);
+ assert_null(bfd_session);
+
+ /* delete the bfd session handle */
+ ret = bfd_session_handle_delete(bfd_session_handle);
+ assert_int_equal(ret, RT_SUCCESS);
+}
+
+/* Test Case: rfc 5881 */
+/*
+ * BFD State Interaction Diagram
+ *
+ * A B
+ * -------- --------
+ * DOWN DOWN
+ * | |
+ * | down->init |
+ * |-----------------------------------|
+ * | |
+ * | |
+ * | BFD DOWN |
+ * |-------------------------> |
+ * | |
+ * | |
+ * | BFD INIT |
+ * |<------------------------- |
+ * | |
+ * | |
+ * | BFD UP |
+ * |-------------------------> |
+ * | |
+ * | |
+ * | BFD UP |
+ * |<------------------------- |
+ * UP UP
+ *
+ * Legend:
+ * - BFD DOWN: BFD Down state packet
+ * - BFD INIT: BFD Init state packet
+ * - BFD UP: BFD Up state packet
+ * - XX->YY: State change from XX to YY
+ */
+static void testcase_bfd_rfc5881(void ** state)
+{
+ struct rte_graph * graph_ptr = rte_graph_lookup("test_bfd");
+ assert_non_null(graph_ptr);
+
+ /* alloc the mbuf as input */
+ struct rte_mbuf * mbuf_input[1];
+ mbuf_input[0] = rte_pktmbuf_alloc(mock_pktmbuf_pool);
+
+ assert_non_null(mbuf_input);
+
+ /* set the return value for the mock function */
+ will_return(mock_pkt_input_process, 1);
+ will_return(mock_pkt_input_process, mbuf_input);
+
+ struct rte_mbuf * mbuf_output[1] = {};
+ unsigned int nr_mbuf_output = 0;
+
+ will_return(mock_pkt_output_process, mbuf_output);
+ will_return(mock_pkt_output_process, &nr_mbuf_output);
+
+ /* set mbuf pkt_len */
+ mbuf_input[0]->data_len = sizeof(struct rte_ether_hdr) + sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_udp_hdr) +
+ sizeof(struct bfd_hdr);
+ mbuf_input[0]->pkt_len = mbuf_input[0]->data_len;
+
+ /* prepare the bfd packet for initialization */
+ struct rte_ether_hdr * ether_hdr = rte_pktmbuf_mtod(mbuf_input[0], struct rte_ether_hdr *);
+ struct rte_ipv4_hdr * ipv4_hdr = (struct rte_ipv4_hdr *)(ether_hdr + 1);
+ struct rte_udp_hdr * udp_hdr = (struct rte_udp_hdr *)(ipv4_hdr + 1);
+ struct bfd_hdr * bfd_hdr = (struct bfd_hdr *)(udp_hdr + 1);
+
+ /*
+ * 1. bfd down to init
+ */
+
+ /* set the ether header */
+ struct rte_ether_addr dst_mac = {.addr_bytes = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66}};
+ struct rte_ether_addr src_mac = {.addr_bytes = {0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}};
+ eth_header_construct(ether_hdr, &src_mac, &dst_mac, RTE_ETHER_TYPE_IPV4);
+
+ /* set the ipv4 header */
+ ipv4_header_construct(ipv4_hdr, NULL, NULL, IPPROTO_UDP);
+
+ /* set the udp header */
+ udp_header_construct(udp_hdr, 3784, 3784, sizeof(struct bfd_hdr));
+
+ /* set the bfd header */
+ uint32_t discriminator_local = 0x12345678;
+ bfd_header_construct(bfd_hdr, &discriminator_local, NULL, BFD_STATE_DOWN);
+
+ struct mrb_metadata * mrb_meta = mrbuf_cz_data(mbuf_input[0], MR_NODE_CTRLZONE_ID);
+ mrb_metadata_clear(mrb_meta);
+ mrb_meta->port_ingress = 1;
+
+ /* prepare to parse the ingress pkt */
+ struct pkt_parser pkt_parser;
+ pkt_parser_init(&pkt_parser, &mrb_meta->pkt_parser_result, LAYER_TYPE_ALL, MR_PKT_PARSER_LAYERS_MAX);
+ pkt_parser_exec(&pkt_parser, mbuf_input[0]);
+
+ rte_graph_walk(graph_ptr);
+
+ /* check the count */
+ assert_int_equal(nr_mbuf_output, 1);
+
+ /* check the mbuf ptr */
+ assert_ptr_equal(mbuf_output[0], mbuf_input[0]);
+
+ /* check the port egress */
+ struct mrb_metadata * mrb_meta_output = mrbuf_cz_data(mbuf_output[0], MR_NODE_CTRLZONE_ID);
+ assert_int_equal(mrb_meta_output->port_egress, 1);
+
+ /* check the eth mac addr */
+ struct rte_ether_hdr * eth_hdr_output = rte_pktmbuf_mtod(mbuf_output[0], struct rte_ether_hdr *);
+ assert_memory_equal(&eth_hdr_output->dst_addr, &src_mac, sizeof(struct rte_ether_addr));
+ assert_memory_equal(&eth_hdr_output->src_addr, &dst_mac, sizeof(struct rte_ether_addr));
+
+ /* check the ipv4 header */
+ uint32_t src_ip, dst_ip;
+ inet_pton(AF_INET, "192.168.1.2", &src_ip);
+ inet_pton(AF_INET, "192.168.1.1", &dst_ip);
+
+ struct rte_ipv4_hdr * ipv4_hdr_output = (struct rte_ipv4_hdr *)(eth_hdr_output + 1);
+ assert_int_equal(ipv4_hdr_output->src_addr, src_ip);
+ assert_int_equal(ipv4_hdr_output->dst_addr, dst_ip);
+ assert_int_equal(ipv4_hdr_output->time_to_live, 255);
+
+ uint16_t output_checksum = ipv4_hdr_output->hdr_checksum;
+ ipv4_hdr_output->hdr_checksum = 0;
+ assert_int_equal(output_checksum, rte_ipv4_cksum(ipv4_hdr_output));
+
+ /* check the udp header */
+ struct rte_udp_hdr * udp_hdr_output = (struct rte_udp_hdr *)(ipv4_hdr_output + 1);
+ assert_int_equal(udp_hdr_output->src_port, rte_cpu_to_be_16(3784));
+ assert_int_equal(udp_hdr_output->dst_port, rte_cpu_to_be_16(3784));
+
+ /* check the bfd header */
+ uint32_t discriminator_checker = 0, discriminator_remote = 0;
+ struct bfd_hdr * bfd_hdr_output = (struct bfd_hdr *)(udp_hdr_output + 1);
+
+ rte_memcpy(&discriminator_checker, bfd_hdr_output->your_discriminator, sizeof(uint32_t));
+ assert_int_equal(discriminator_checker, rte_cpu_to_be_32(discriminator_local));
+
+ rte_memcpy(&discriminator_checker, bfd_hdr_output->my_discriminator, sizeof(uint32_t));
+ assert_ptr_not_equal(discriminator_checker, 0);
+ discriminator_remote = discriminator_checker;
+
+ assert_int_equal(bfd_hdr_output->state, BFD_STATE_INIT);
+
+ /*
+ * 2. bfd init to up
+ */
+
+ will_return(mock_pkt_input_process, 1);
+ will_return(mock_pkt_input_process, mbuf_input);
+
+ will_return(mock_pkt_output_process, mbuf_output);
+ will_return(mock_pkt_output_process, &nr_mbuf_output);
+
+ /* swap the eth mac addr */
+ struct rte_ether_addr swap_addr;
+ rte_ether_addr_copy(&eth_hdr_output->dst_addr, &swap_addr);
+ rte_ether_addr_copy(&eth_hdr_output->src_addr, &eth_hdr_output->dst_addr);
+ rte_ether_addr_copy(&swap_addr, &eth_hdr_output->src_addr);
+
+ /* swap ipv4_hdr */
+ uint32_t swap_ip = ipv4_hdr_output->src_addr;
+ ipv4_hdr_output->src_addr = ipv4_hdr_output->dst_addr;
+ ipv4_hdr_output->dst_addr = swap_ip;
+ ipv4_hdr_output->time_to_live = 255;
+ ipv4_hdr_output->hdr_checksum = rte_ipv4_cksum(ipv4_hdr_output);
+
+ /* swap bfd discriminator */
+ uint32_t discriminator_swap = 0;
+ rte_memcpy(&discriminator_swap, bfd_hdr_output->my_discriminator, sizeof(uint32_t));
+ rte_memcpy(bfd_hdr_output->my_discriminator, &bfd_hdr_output->your_discriminator, sizeof(uint32_t));
+ rte_memcpy(&bfd_hdr_output->your_discriminator, &discriminator_swap, sizeof(uint32_t));
+ bfd_hdr_output->state = BFD_STATE_UP;
+
+ rte_graph_walk(graph_ptr);
+
+ /* check the count */
+ assert_int_equal(nr_mbuf_output, 1);
+
+ /* check the mbuf ptr */
+ assert_ptr_equal(mbuf_output[0], mbuf_input[0]);
+
+ /* check the port egress */
+ mrb_meta_output = mrbuf_cz_data(mbuf_output[0], MR_NODE_CTRLZONE_ID);
+ assert_int_equal(mrb_meta_output->port_egress, 1);
+
+ /* check the eth mac addr */
+ assert_memory_equal(&eth_hdr_output->dst_addr, &src_mac, sizeof(struct rte_ether_addr));
+ assert_memory_equal(&eth_hdr_output->src_addr, &dst_mac, sizeof(struct rte_ether_addr));
+
+ /* check the ipv4 header */
+ assert_int_equal(ipv4_hdr_output->src_addr, src_ip);
+ assert_int_equal(ipv4_hdr_output->dst_addr, dst_ip);
+ assert_int_equal(ipv4_hdr_output->time_to_live, 255);
+
+ output_checksum = ipv4_hdr_output->hdr_checksum;
+ ipv4_hdr_output->hdr_checksum = 0;
+ assert_int_equal(output_checksum, rte_ipv4_cksum(ipv4_hdr_output));
+
+ /* check the udp header */
+ assert_int_equal(udp_hdr_output->src_port, rte_cpu_to_be_16(3784));
+ assert_int_equal(udp_hdr_output->dst_port, rte_cpu_to_be_16(3784));
+
+ /* check the bfd header */
+ rte_memcpy(&discriminator_checker, bfd_hdr_output->your_discriminator, sizeof(uint32_t));
+ assert_int_equal(discriminator_checker, rte_cpu_to_be_32(discriminator_local));
+
+ rte_memcpy(&discriminator_checker, bfd_hdr_output->my_discriminator, sizeof(uint32_t));
+ assert_int_equal(discriminator_checker, discriminator_remote);
+
+ assert_int_equal(bfd_hdr_output->state, BFD_STATE_UP);
+
+ /* release the mbufs */
+ rte_pktmbuf_free_bulk(mbuf_output, 1);
+
+ assert_true(rte_mempool_full(mock_pktmbuf_pool) == 1);
+}
+
+/* Test Case: bfd session full */
+static void testcase_bfd_session_full(void ** state)
+{
+ struct rte_graph * graph_ptr = rte_graph_lookup("test_bfd");
+ assert_non_null(graph_ptr);
+
+ /* alloc the mbuf as input */
+ struct rte_mbuf * mbuf_input[1];
+ mbuf_input[0] = rte_pktmbuf_alloc(mock_pktmbuf_pool);
+
+ assert_non_null(mbuf_input);
+
+ /* set the return value for the mock function */
+ will_return(mock_pkt_input_process, 1);
+ will_return(mock_pkt_input_process, mbuf_input);
+
+ struct rte_mbuf * mbuf_output[1] = {};
+ unsigned int nr_mbuf_output = 0;
+
+ will_return(mock_pkt_output_process, mbuf_output);
+ will_return(mock_pkt_output_process, &nr_mbuf_output);
+
+ struct rte_mbuf * mbuf_drop[1] = {};
+ unsigned int nr_mbuf_drop = 0;
+ will_return(mock_pkt_drop_process, mbuf_drop);
+ will_return(mock_pkt_drop_process, &nr_mbuf_drop);
+
+ /* set mbuf pkt_len */
+ mbuf_input[0]->data_len = sizeof(struct rte_ether_hdr) + sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_udp_hdr) +
+ sizeof(struct bfd_hdr);
+ mbuf_input[0]->pkt_len = mbuf_input[0]->data_len;
+
+ /* prepare the bfd packet for initialization */
+ struct rte_ether_hdr * ether_hdr = rte_pktmbuf_mtod(mbuf_input[0], struct rte_ether_hdr *);
+ struct rte_ipv4_hdr * ipv4_hdr = (struct rte_ipv4_hdr *)(ether_hdr + 1);
+ struct rte_udp_hdr * udp_hdr = (struct rte_udp_hdr *)(ipv4_hdr + 1);
+ struct bfd_hdr * bfd_hdr = (struct bfd_hdr *)(udp_hdr + 1);
+
+ /* set the ether header */
+ eth_header_construct(ether_hdr, NULL, NULL, RTE_ETHER_TYPE_IPV4);
+
+ /* set the ipv4 header */
+ ipv4_header_construct(ipv4_hdr, "172.16.1.1", NULL, IPPROTO_UDP);
+
+ /* set the udp header */
+ udp_header_construct(udp_hdr, 3784, 3784, sizeof(struct bfd_hdr));
+
+ /* set the bfd header */
+ uint32_t discriminator_local = 0x12345678;
+ bfd_header_construct(bfd_hdr, &discriminator_local, NULL, BFD_STATE_DOWN);
+
+ /* set the metadata */
+ struct mrb_metadata * mrb_meta = mrbuf_cz_data(mbuf_input[0], MR_NODE_CTRLZONE_ID);
+ mrb_metadata_clear(mrb_meta);
+ mrb_meta->port_ingress = 1;
+
+ /* prepare to parse the ingress pkt */
+ struct pkt_parser pkt_parser;
+ pkt_parser_init(&pkt_parser, &mrb_meta->pkt_parser_result, LAYER_TYPE_ALL, MR_PKT_PARSER_LAYERS_MAX);
+ pkt_parser_exec(&pkt_parser, mbuf_input[0]);
+
+ /* simulate the scenario where the current BFD session handle is full */
+ struct bfd_test_main * bfd_test_main = *state;
+ struct bfd_session_handle * bfd_session_handle = bfd_session_handle_get(bfd_test_main->bfd_node_main, 0);
+ assert_non_null(bfd_session_handle);
+
+ while (bfd_session_handle_is_full(bfd_session_handle) == 0)
+ {
+ bfd_session_add(bfd_session_handle, 1);
+ }
+
+ assert_int_equal(bfd_session_handle_is_full(bfd_session_handle), 1);
+
+ /* run graph */
+ rte_graph_walk(graph_ptr);
+
+ /* check the count */
+ assert_int_equal(nr_mbuf_drop, 1);
+
+ /* check the mbuf ptr */
+ assert_ptr_equal(mbuf_drop[0], mbuf_input[0]);
+
+ /* release the mbufs */
+ rte_pktmbuf_free_bulk(mbuf_drop, 1);
+
+ assert_true(rte_mempool_full(mock_pktmbuf_pool) == 1);
+}
+
+/* Test Case: discriminator method flipping */
+/*
+ * BFD State Interaction Diagram
+ *
+ * A B
+ * -------- --------
+ * DOWN DOWN
+ * | |
+ * | down->init |
+ * |-----------------------------------|
+ * | |
+ * | |
+ * | BFD DOWN |
+ * |-------------------------> |
+ * | |
+ * | |
+ * | BFD INIT |
+ * |<------------------------- |
+ * | |
+ * | |
+ * | BFD UP |
+ * |-------------------------> |
+ * | |
+ * | |
+ * | BFD UP |
+ * |<------------------------- |
+ * UP UP
+ *
+ * Legend:
+ * - BFD DOWN: BFD Down state packet
+ * - BFD INIT: BFD Init state packet
+ * - BFD UP: BFD Up state packet
+ * - XX->YY: State change from XX to YY
+ */
+static void testcase_bfd_discriminator_method_flipping(void ** state)
+{
+ struct rte_graph * graph_ptr = rte_graph_lookup("test_bfd");
+ assert_non_null(graph_ptr);
+
+ /* alloc the mbuf as input */
+ struct rte_mbuf * mbuf_input[1];
+ mbuf_input[0] = rte_pktmbuf_alloc(mock_pktmbuf_pool);
+
+ assert_non_null(mbuf_input);
+
+ /* set the return value for the mock function */
+ will_return(mock_pkt_input_process, 1);
+ will_return(mock_pkt_input_process, mbuf_input);
+
+ struct rte_mbuf * mbuf_output[1] = {};
+ unsigned int nr_mbuf_output = 0;
+
+ will_return(mock_pkt_output_process, mbuf_output);
+ will_return(mock_pkt_output_process, &nr_mbuf_output);
+
+ /* set mbuf pkt_len */
+ mbuf_input[0]->data_len = sizeof(struct rte_ether_hdr) + sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_udp_hdr) +
+ sizeof(struct bfd_hdr);
+ mbuf_input[0]->pkt_len = mbuf_input[0]->data_len;
+
+ /* prepare the bfd packet for initialization */
+ struct rte_ether_hdr * ether_hdr = rte_pktmbuf_mtod(mbuf_input[0], struct rte_ether_hdr *);
+ struct rte_ipv4_hdr * ipv4_hdr = (struct rte_ipv4_hdr *)(ether_hdr + 1);
+ struct rte_udp_hdr * udp_hdr = (struct rte_udp_hdr *)(ipv4_hdr + 1);
+ struct bfd_hdr * bfd_hdr = (struct bfd_hdr *)(udp_hdr + 1);
+
+ /*
+ * 1. bfd down to init
+ */
+
+ /* set the ether header */
+ struct rte_ether_addr dst_mac = {.addr_bytes = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66}};
+ struct rte_ether_addr src_mac = {.addr_bytes = {0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}};
+ eth_header_construct(ether_hdr, &src_mac, &dst_mac, RTE_ETHER_TYPE_IPV4);
+
+ /* set the ipv4 header */
+ ipv4_header_construct(ipv4_hdr, NULL, NULL, IPPROTO_UDP);
+
+ /* set the udp header */
+ udp_header_construct(udp_hdr, 3784, 3784, sizeof(struct bfd_hdr));
+
+ /* set the bfd header */
+ uint32_t discriminator_local = 0x12345678, discriminator_remote = 0x87654321;
+ bfd_header_construct(bfd_hdr, &discriminator_local, &discriminator_remote, BFD_STATE_DOWN);
+
+ struct mrb_metadata * mrb_meta = mrbuf_cz_data(mbuf_input[0], MR_NODE_CTRLZONE_ID);
+ mrb_metadata_clear(mrb_meta);
+ mrb_meta->port_ingress = 1;
+
+ /* prepare to parse the ingress pkt */
+ struct pkt_parser pkt_parser;
+ pkt_parser_init(&pkt_parser, &mrb_meta->pkt_parser_result, LAYER_TYPE_ALL, MR_PKT_PARSER_LAYERS_MAX);
+ pkt_parser_exec(&pkt_parser, mbuf_input[0]);
+
+ rte_graph_walk(graph_ptr);
+
+ /* check the count */
+ assert_int_equal(nr_mbuf_output, 1);
+
+ /* check the mbuf ptr */
+ assert_ptr_equal(mbuf_output[0], mbuf_input[0]);
+
+ /* check the port egress */
+ struct mrb_metadata * mrb_meta_output = mrbuf_cz_data(mbuf_output[0], MR_NODE_CTRLZONE_ID);
+ assert_int_equal(mrb_meta_output->port_egress, 1);
+
+ /* check the eth mac addr */
+ struct rte_ether_hdr * eth_hdr_output = rte_pktmbuf_mtod(mbuf_output[0], struct rte_ether_hdr *);
+ assert_memory_equal(&eth_hdr_output->dst_addr, &src_mac, sizeof(struct rte_ether_addr));
+ assert_memory_equal(&eth_hdr_output->src_addr, &dst_mac, sizeof(struct rte_ether_addr));
+
+ /* check the ipv4 header */
+ uint32_t src_ip, dst_ip;
+ inet_pton(AF_INET, "192.168.1.2", &src_ip);
+ inet_pton(AF_INET, "192.168.1.1", &dst_ip);
+
+ struct rte_ipv4_hdr * ipv4_hdr_output = (struct rte_ipv4_hdr *)(eth_hdr_output + 1);
+ assert_int_equal(ipv4_hdr_output->src_addr, src_ip);
+ assert_int_equal(ipv4_hdr_output->dst_addr, dst_ip);
+ assert_int_equal(ipv4_hdr_output->time_to_live, 255);
+
+ uint16_t output_checksum = ipv4_hdr_output->hdr_checksum;
+ ipv4_hdr_output->hdr_checksum = 0;
+ assert_int_equal(output_checksum, rte_ipv4_cksum(ipv4_hdr_output));
+
+ /* check the udp header */
+ struct rte_udp_hdr * udp_hdr_output = (struct rte_udp_hdr *)(ipv4_hdr_output + 1);
+ assert_int_equal(udp_hdr_output->src_port, rte_cpu_to_be_16(3784));
+ assert_int_equal(udp_hdr_output->dst_port, rte_cpu_to_be_16(3784));
+
+ /* check the bfd header */
+ uint32_t discriminator_checker = 0;
+ struct bfd_hdr * bfd_hdr_output = (struct bfd_hdr *)(udp_hdr_output + 1);
+
+ rte_memcpy(&discriminator_checker, bfd_hdr_output->your_discriminator, sizeof(uint32_t));
+ assert_int_equal(discriminator_checker, rte_cpu_to_be_32(discriminator_local));
+
+ rte_memcpy(&discriminator_checker, bfd_hdr_output->my_discriminator, sizeof(uint32_t));
+ assert_ptr_not_equal(discriminator_checker, rte_cpu_to_be_32(discriminator_remote));
+
+ assert_int_equal(bfd_hdr_output->state, BFD_STATE_INIT);
+
+ /*
+ * 2. bfd init to up
+ */
+
+ will_return(mock_pkt_input_process, 1);
+ will_return(mock_pkt_input_process, mbuf_input);
+
+ will_return(mock_pkt_output_process, mbuf_output);
+ will_return(mock_pkt_output_process, &nr_mbuf_output);
+
+ /* swap the eth mac addr */
+ struct rte_ether_addr swap_addr;
+ rte_ether_addr_copy(&eth_hdr_output->dst_addr, &swap_addr);
+ rte_ether_addr_copy(&eth_hdr_output->src_addr, &eth_hdr_output->dst_addr);
+ rte_ether_addr_copy(&swap_addr, &eth_hdr_output->src_addr);
+
+ /* swap ipv4_hdr */
+ uint32_t swap_ip = ipv4_hdr_output->src_addr;
+ ipv4_hdr_output->src_addr = ipv4_hdr_output->dst_addr;
+ ipv4_hdr_output->dst_addr = swap_ip;
+ ipv4_hdr_output->time_to_live = 255;
+ ipv4_hdr_output->hdr_checksum = rte_ipv4_cksum(ipv4_hdr_output);
+
+ /* swap bfd discriminator */
+ uint32_t discriminator_swap = 0;
+ rte_memcpy(&discriminator_swap, bfd_hdr_output->my_discriminator, sizeof(uint32_t));
+ rte_memcpy(bfd_hdr_output->my_discriminator, &bfd_hdr_output->your_discriminator, sizeof(uint32_t));
+ rte_memcpy(&bfd_hdr_output->your_discriminator, &discriminator_swap, sizeof(uint32_t));
+ bfd_hdr_output->state = BFD_STATE_UP;
+
+ rte_graph_walk(graph_ptr);
+
+ /* check the count */
+ assert_int_equal(nr_mbuf_output, 1);
+
+ /* check the mbuf ptr */
+ assert_ptr_equal(mbuf_output[0], mbuf_input[0]);
+
+ /* check the port egress */
+ mrb_meta_output = mrbuf_cz_data(mbuf_output[0], MR_NODE_CTRLZONE_ID);
+ assert_int_equal(mrb_meta_output->port_egress, 1);
+
+ /* check the eth mac addr */
+ assert_memory_equal(&eth_hdr_output->dst_addr, &src_mac, sizeof(struct rte_ether_addr));
+ assert_memory_equal(&eth_hdr_output->src_addr, &dst_mac, sizeof(struct rte_ether_addr));
+
+ /* check the ipv4 header */
+ assert_int_equal(ipv4_hdr_output->src_addr, src_ip);
+ assert_int_equal(ipv4_hdr_output->dst_addr, dst_ip);
+ assert_int_equal(ipv4_hdr_output->time_to_live, 255);
+
+ output_checksum = ipv4_hdr_output->hdr_checksum;
+ ipv4_hdr_output->hdr_checksum = 0;
+ assert_int_equal(output_checksum, rte_ipv4_cksum(ipv4_hdr_output));
+
+ /* check the udp header */
+ assert_int_equal(udp_hdr_output->src_port, rte_cpu_to_be_16(3784));
+ assert_int_equal(udp_hdr_output->dst_port, rte_cpu_to_be_16(3784));
+
+ /* check the bfd header */
+ rte_memcpy(&discriminator_checker, bfd_hdr_output->your_discriminator, sizeof(uint32_t));
+ assert_int_equal(discriminator_checker, rte_cpu_to_be_32(discriminator_local));
+
+ rte_memcpy(&discriminator_checker, bfd_hdr_output->my_discriminator, sizeof(uint32_t));
+ assert_ptr_not_equal(discriminator_checker, rte_cpu_to_be_32(discriminator_remote));
+
+ assert_int_equal(bfd_hdr_output->state, BFD_STATE_UP);
+
+ /* release the mbufs */
+ rte_pktmbuf_free_bulk(mbuf_output, 1);
+
+ assert_true(rte_mempool_full(mock_pktmbuf_pool) == 1);
+}
+
+int main(int argc, char * argv[])
+{
+ /* generate the eal args */
+ static char * eal_args[] = {
+ "test_node_bfd",
+ "-c 0x1",
+ "--no-huge",
+ "--no-pci",
+ };
+
+ /* run the eal with no huge pages */
+ int ret = rte_eal_init(RTE_DIM(eal_args), eal_args);
+ if (ret < 0)
+ {
+ rte_exit(EXIT_FAILURE, "Error with EAL initialization\n");
+ }
+
+ mock_pktmbuf_pool = rte_pktmbuf_pool_create("test_pktmbuf_pool", 1024, 32, 128, RTE_MBUF_DEFAULT_BUF_SIZE,
+ SOCKET_ID_ANY);
+
+ const struct CMUnitTest group_bfd_node_main[] = {
+ cmocka_unit_test(testcase_bfd_node_main_api_success),
+ cmocka_unit_test(testcase_bfd_node_main_api_failure),
+ };
+
+ const struct CMUnitTest group_bfd_session_handle[] = {
+ cmocka_unit_test(testcase_bfd_session_handle_api_success),
+ cmocka_unit_test(testcase_bfd_session_handle_api_failure),
+ };
+
+ const struct CMUnitTest group_bfd_init[] = {
+ cmocka_unit_test(testcase_bfd_init_success),
+ cmocka_unit_test(testcase_bfd_deinit_success),
+ };
+
+ const struct CMUnitTest group_bfd_discriminator_method_rfc[] = {
+
+ cmocka_unit_test(testcase_bfd_rfc5881),
+ cmocka_unit_test(testcase_bfd_session_full),
+ };
+
+ const struct CMUnitTest group_bfd_discriminator_method_flipping[] = {
+
+ cmocka_unit_test(testcase_bfd_discriminator_method_flipping),
+ };
+
+ cmocka_run_group_tests(group_bfd_node_main, NULL, NULL);
+ cmocka_run_group_tests(group_bfd_session_handle, NULL, NULL);
+ cmocka_run_group_tests(group_bfd_init, testgroup_bfd_init_setup, testgroup_bfd_init_teardown);
+ cmocka_run_group_tests(group_bfd_discriminator_method_rfc, testgroup_bfd_discriminator_method_rfc_setup,
+ testgroup_bfd_discriminator_method_rfc_teardown);
+ cmocka_run_group_tests(group_bfd_discriminator_method_flipping, testgroup_bfd_discriminator_method_flipping_setup,
+ testgroup_bfd_discriminator_method_flipping_teardown);
+
+ rte_eal_cleanup();
+ return 0;
+}
diff --git a/service/test/test_sc_metrics.c b/service/test/test_sc_metrics.c
new file mode 100644
index 0000000..b0e7d51
--- /dev/null
+++ b/service/test/test_sc_metrics.c
@@ -0,0 +1,128 @@
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <cmocka.h>
+
+#include <rte_eal.h>
+#include <rte_mempool.h>
+
+#include <sc_metrics.h>
+#include <sc_node.h>
+#include <sc_node_common.h>
+
+/* Test Case: Sc metrics API in a successful scenario */
+static void testcase_sc_metrics_api_success(void ** state)
+{
+ /* create a new sc metrics handle */
+ struct sc_metrics_handle * metrics_handle = sc_metrics_handle_create(7);
+ assert_non_null(metrics_handle);
+
+ /* check the capacity of the metrics handle,To ensure cache line alignment, the capacity must be a multiple of 8. */
+ assert_int_equal(metrics_handle->capacity % 8, 0);
+
+ /* set the values for the metrics handle */
+ uint64_t values[7] = {1, 2, 3, 4, 5, 6, 7};
+ int ret = sc_metrics_values_set(metrics_handle, values, 7);
+ assert_int_equal(ret, RT_SUCCESS);
+
+ /* get the values from the metrics handle */
+ uint64_t get_values[7] = {0};
+ ret = sc_metrics_values_get(metrics_handle, get_values, 7);
+ assert_int_equal(ret, RT_SUCCESS);
+
+ /* check the values */
+ for (int i = 0; i < 7; i++)
+ {
+ assert_int_equal(values[i], get_values[i]);
+ }
+
+ /* accumulate the values */
+ uint64_t inc_values[7] = {1, 2, 3, 4, 5, 6, 7};
+ ret = sc_metrics_accumulate(metrics_handle, inc_values, 7);
+ assert_int_equal(ret, RT_SUCCESS);
+
+ /* get the values from the metrics handle */
+ ret = sc_metrics_values_get(metrics_handle, get_values, 7);
+ assert_int_equal(ret, RT_SUCCESS);
+
+ /* check the values */
+ for (int i = 0; i < 7; i++)
+ {
+ assert_int_equal(values[i] + inc_values[i], get_values[i]);
+ }
+
+ /* Set the metric value with the key 0 */
+ ret = sc_metrics_value_set(metrics_handle, 100, 0);
+ assert_int_equal(ret, RT_SUCCESS);
+
+ /* get the value from the metrics handle */
+ uint64_t get_value = 0;
+ ret = sc_metrics_value_get(metrics_handle, &get_value, 0);
+ assert_int_equal(ret, RT_SUCCESS);
+
+ /* check the value */
+ assert_int_equal(100, get_value);
+
+ /* reset the values */
+ ret = sc_metrics_values_reset(metrics_handle);
+ assert_int_equal(ret, RT_SUCCESS);
+
+ /* get the values from the metrics handle */
+ ret = sc_metrics_values_get(metrics_handle, get_values, 7);
+ assert_int_equal(ret, RT_SUCCESS);
+
+ /* check the values */
+ for (int i = 0; i < 7; i++)
+ {
+ assert_int_equal(0, get_values[i]);
+ }
+
+ /* delete the metrics handle */
+ ret = sc_metrics_handle_delete(metrics_handle);
+ assert_int_equal(ret, RT_SUCCESS);
+}
+
+/* Test Case: Sc metrics API in a failure scenario */
+static void testcase_sc_metrics_api_failure(void ** state)
+{
+ /* create a new sc metrics handle */
+ struct sc_metrics_handle * metrics_handle = sc_metrics_handle_create(0);
+ assert_null(metrics_handle);
+
+ metrics_handle = sc_metrics_handle_create(SC_METRICS_MAX_CAPACITY + 1);
+ assert_null(metrics_handle);
+
+ /* delete the metrics handle */
+ int ret = sc_metrics_handle_delete(NULL);
+ assert_int_equal(ret, RT_ERR);
+}
+
+int main(int argc, char * argv[])
+{
+ /* generate the eal args */
+ static char * eal_args[] = {
+ "test_node_bfd",
+ "-c 0x1",
+ "--no-huge",
+ "--no-pci",
+ };
+
+ /* run the eal with no huge pages */
+ int ret = rte_eal_init(RTE_DIM(eal_args), eal_args);
+ if (ret < 0)
+ {
+ rte_exit(EXIT_FAILURE, "Error with EAL initialization\n");
+ }
+
+ const struct CMUnitTest group_sc_metrics[] = {
+ cmocka_unit_test(testcase_sc_metrics_api_success),
+ cmocka_unit_test(testcase_sc_metrics_api_failure),
+ };
+
+ cmocka_run_group_tests(group_sc_metrics, NULL, NULL);
+ rte_eal_cleanup();
+ return 0;
+}
diff --git a/test/ptf_test/CMakeLists.txt b/test/ptf_test/CMakeLists.txt
index a328237..30d45a5 100644
--- a/test/ptf_test/CMakeLists.txt
+++ b/test/ptf_test/CMakeLists.txt
@@ -55,7 +55,7 @@ add_test(
NAME bfd_test
COMMAND /usr/local/bin/ptf --test-dir ${CMAKE_CURRENT_SOURCE_DIR}
--interface 0@veth0-ptf0
- bfd_test_for_etherfabric bfd_test_for_bfdd --test-params "source_dir='${CMAKE_BINARY_DIR}'"
+ bfd_test_for_etherfabric bfd_test_for_bfdd bfd_test_for_ttl --test-params "source_dir='${CMAKE_BINARY_DIR}'"
)
# Add bridge test