/* * Copyright (c) 2022-2026 NVIDIA CORPORATION AND AFFILIATES. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted * provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, this list of * conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used * to endorse or promote products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include #include #include #include #include DOCA_LOG_REGISTER(FLOW_LPM); /* * Create DOCA Flow main pipe * * @port [in]: port of the pipe * @next_pipe [in]: lpm pipe to forward the matched traffic * @pipe [out]: created pipe pointer * @return: DOCA_SUCCESS on success and DOCA_ERROR otherwise. */ static doca_error_t create_main_pipe(struct doca_flow_port *port, struct doca_flow_pipe *next_pipe, struct doca_flow_pipe **pipe) { struct doca_flow_pipe_cfg *pipe_cfg; struct doca_flow_match match; struct doca_flow_monitor counter; struct doca_flow_fwd fwd; doca_error_t result; memset(&match, 0, sizeof(match)); memset(&counter, 0, sizeof(counter)); memset(&fwd, 0, sizeof(fwd)); match.parser_meta.outer_l3_type = DOCA_FLOW_L3_META_IPV4; counter.counter_type = DOCA_FLOW_RESOURCE_TYPE_NON_SHARED; fwd.type = DOCA_FLOW_FWD_PIPE; fwd.next_pipe = next_pipe; result = doca_flow_pipe_cfg_create(&pipe_cfg, port); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to create doca_flow_pipe_cfg: %s", doca_error_get_descr(result)); return result; } result = set_flow_pipe_cfg(pipe_cfg, "MAIN_PIPE", DOCA_FLOW_PIPE_BASIC, true); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to set doca_flow_pipe_cfg: %s", doca_error_get_descr(result)); goto destroy_pipe_cfg; } result = doca_flow_pipe_cfg_set_match(pipe_cfg, &match, NULL); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to set doca_flow_pipe_cfg match: %s", doca_error_get_descr(result)); goto destroy_pipe_cfg; } result = doca_flow_pipe_cfg_set_monitor(pipe_cfg, &counter); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to set doca_flow_pipe_cfg monitor: %s", doca_error_get_descr(result)); goto destroy_pipe_cfg; } result = doca_flow_pipe_create(pipe_cfg, &fwd, NULL, pipe); destroy_pipe_cfg: doca_flow_pipe_cfg_destroy(pipe_cfg); return result; } /* * Add DOCA Flow pipe entry to the main pipe that forwards ipv4 traffic to lpm pipe * * @pipe [in]: pipe of the entry * @status [in]: user context for adding entry * @entry [out]: result of entry addition * @return: DOCA_SUCCESS on success and DOCA_ERROR otherwise. */ static doca_error_t add_main_pipe_entry(struct doca_flow_pipe *pipe, struct entries_status *status, struct doca_flow_pipe_entry **entry) { struct doca_flow_match match; memset(&match, 0, sizeof(match)); return doca_flow_pipe_basic_add_entry(0, pipe, &match, 0, NULL, NULL, NULL, DOCA_FLOW_ENTRY_FLAGS_NO_WAIT, status, entry); } /* * Add DOCA Flow LPM pipe that matched IPV4 addresses * * @port [in]: port of the pipe * @port_id [in]: port ID of the pipe * @pipe [out]: created pipe pointer * @return: DOCA_SUCCESS on success and DOCA_ERROR otherwise. */ static doca_error_t create_lpm_pipe(struct doca_flow_port *port, int port_id, struct doca_flow_pipe **pipe) { (void)port_id; struct doca_flow_match match; struct doca_flow_monitor counter; struct doca_flow_pipe_cfg *pipe_cfg; struct doca_flow_fwd fwd = {.type = DOCA_FLOW_FWD_CHANGEABLE}; doca_error_t result; memset(&match, 0, sizeof(match)); memset(&counter, 0, sizeof(counter)); match.outer.l3_type = DOCA_FLOW_L3_TYPE_IP4; match.outer.ip4.src_ip = 0xffffffff; counter.counter_type = DOCA_FLOW_RESOURCE_TYPE_NON_SHARED; result = doca_flow_pipe_cfg_create(&pipe_cfg, port); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to create doca_flow_pipe_cfg: %s", doca_error_get_descr(result)); return result; } result = set_flow_pipe_cfg(pipe_cfg, "LPM_PIPE", DOCA_FLOW_PIPE_LPM, false); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to set doca_flow_pipe_cfg: %s", doca_error_get_descr(result)); goto destroy_pipe_cfg; } result = doca_flow_pipe_cfg_set_nr_entries(pipe_cfg, FLOW_COMMON_PIPE_RULES); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to set doca_flow_pipe_cfg number of entries: %s", doca_error_get_descr(result)); goto destroy_pipe_cfg; } result = doca_flow_pipe_cfg_set_match(pipe_cfg, &match, NULL); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to set doca_flow_pipe_cfg match: %s", doca_error_get_descr(result)); goto destroy_pipe_cfg; } result = doca_flow_pipe_cfg_set_monitor(pipe_cfg, &counter); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to set doca_flow_pipe_cfg monitor: %s", doca_error_get_descr(result)); goto destroy_pipe_cfg; } /* No pipe actions; no need to set actions in the pipe config. */ result = doca_flow_pipe_create(pipe_cfg, &fwd, NULL, pipe); destroy_pipe_cfg: doca_flow_pipe_cfg_destroy(pipe_cfg); return result; } /* * Add DOCA Flow pipe entries to the LPM pipe. one entry with full mask and one with 16 bits mask * * @pipe [in]: pipe of the entry * @port_id [in]: port ID of the entry * @status [in]: user context for adding entry * @entries [out]: array of pointers to created entries * @return: DOCA_SUCCESS on success and DOCA_ERROR otherwise. */ static doca_error_t add_lpm_pipe_entries(struct doca_flow_pipe *pipe, int port_id, struct entries_status *status, struct doca_flow_pipe_entry **entries) { struct doca_flow_match match; struct doca_flow_match match_mask; struct doca_flow_fwd fwd; doca_be32_t src_ip_addr = BE_IPV4_ADDR(1, 2, 3, 4); doca_error_t result; memset(&match, 0, sizeof(match)); memset(&match_mask, 0, sizeof(match_mask)); memset(&fwd, 0, sizeof(fwd)); match.outer.ip4.src_ip = src_ip_addr; /* add entry with full mask and fwd port */ match_mask.outer.ip4.src_ip = DOCA_HTOBE32(0xffffffff); fwd.type = DOCA_FLOW_FWD_PORT; fwd.port_id = port_id ^ 1; result = doca_flow_pipe_lpm_add_entry(0, pipe, &match, &match_mask, 0, NULL, NULL, &fwd, DOCA_FLOW_ENTRY_FLAGS_WAIT_FOR_BATCH, status, &entries[0]); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to add lpm pipe entry: %s", doca_error_get_descr(result)); return result; } /* add entry with mask on 16 MSBits and fwd drop */ match_mask.outer.ip4.src_ip = DOCA_HTOBE32(0xffff0000); fwd.type = DOCA_FLOW_FWD_DROP; result = doca_flow_pipe_lpm_add_entry(0, pipe, &match, &match_mask, 0, NULL, NULL, &fwd, DOCA_FLOW_ENTRY_FLAGS_NO_WAIT, status, &entries[1]); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to add lpm pipe entry: %s", doca_error_get_descr(result)); return result; } /* add default entry with 0 bits mask and fwd drop */ match_mask.outer.ip4.src_ip = DOCA_HTOBE32(0x00000000); fwd.type = DOCA_FLOW_FWD_DROP; result = doca_flow_pipe_lpm_add_entry(0, pipe, &match, &match_mask, 0, NULL, NULL, &fwd, DOCA_FLOW_ENTRY_FLAGS_NO_WAIT, status, &entries[2]); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to add lpm pipe entry: %s", doca_error_get_descr(result)); return result; } return DOCA_SUCCESS; } /* * Run flow_lpm sample * * @nb_queues [in]: number of queues the sample will use * @return: DOCA_SUCCESS on success and DOCA_ERROR otherwise. */ /* Context structure for statistics printing */ struct lpm_stats_context { int nb_ports; int num_of_entries; struct doca_flow_pipe_entry *(*entries)[4]; }; /* * Print LPM statistics * * @nb_ports [in]: number of ports * @num_of_entries [in]: number of entries per port * @entries [in]: array of flow entries */ static void print_lpm_stats(int nb_ports, int num_of_entries, struct doca_flow_pipe_entry *entries[][4]) { doca_error_t result; struct doca_flow_resource_query stats; int port_id, lpm_entry_id; for (port_id = 0; port_id < nb_ports; port_id++) { result = doca_flow_resource_query_entry(entries[port_id][0], &stats); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Port %d failed to query main pipe entry: %s", port_id, doca_error_get_descr(result)); return; } DOCA_LOG_INFO("Port %d, main pipe entry received %lu packets", port_id, stats.counter.total_pkts); for (lpm_entry_id = 1; lpm_entry_id < num_of_entries; lpm_entry_id++) { result = doca_flow_resource_query_entry(entries[port_id][lpm_entry_id], &stats); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Port %d failed to query LPM pipe entry %d: %s", port_id, lpm_entry_id - 1, doca_error_get_descr(result)); return; } DOCA_LOG_INFO("Port %d, LPM pipe entry %d received %lu packets", port_id, lpm_entry_id - 1, stats.counter.total_pkts); } } } /* * Wrapper function for statistics printing compatible with flow_wait_for_packets * * @context [in]: lpm_stats_context structure */ static void print_lpm_stats_wrapper(void *context) { struct lpm_stats_context *ctx = (struct lpm_stats_context *)context; print_lpm_stats(ctx->nb_ports, ctx->num_of_entries, ctx->entries); } doca_error_t flow_lpm(int nb_queues) { const int nb_ports = 2; /* 1 entry for main pipe and 3 entries for LPM pipe */ const int num_of_entries = 4; struct flow_resources resource = {.mode = DOCA_FLOW_RESOURCE_MODE_PORT, .nr_counters = num_of_entries}; struct doca_flow_pipe_entry *entries[nb_ports][num_of_entries]; uint32_t nr_shared_resources[SHARED_RESOURCE_NUM_VALUES] = {0}; struct doca_flow_port *ports[nb_ports]; uint32_t actions_mem_size[nb_ports]; struct doca_flow_pipe *main_pipe; struct doca_flow_pipe *lpm_pipe; struct entries_status status; doca_error_t result; int port_id; result = init_doca_flow(nb_queues, "vnf,hws", &resource, nr_shared_resources); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to init DOCA Flow: %s", doca_error_get_descr(result)); return result; } ARRAY_INIT(actions_mem_size, ACTIONS_MEM_SIZE(num_of_entries)); result = init_doca_flow_vnf_ports(nb_ports, ports, actions_mem_size, &resource); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to init DOCA ports: %s", doca_error_get_descr(result)); doca_flow_destroy(); return result; } for (port_id = 0; port_id < nb_ports; port_id++) { memset(&status, 0, sizeof(status)); result = create_lpm_pipe(ports[port_id], port_id, &lpm_pipe); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to create pipe: %s", doca_error_get_descr(result)); stop_doca_flow_ports(nb_ports, ports); doca_flow_destroy(); return result; } result = add_lpm_pipe_entries(lpm_pipe, port_id, &status, &entries[port_id][1]); if (result != DOCA_SUCCESS) { stop_doca_flow_ports(nb_ports, ports); doca_flow_destroy(); return result; } result = create_main_pipe(ports[port_id], lpm_pipe, &main_pipe); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to create main pipe: %s", doca_error_get_descr(result)); stop_doca_flow_ports(nb_ports, ports); doca_flow_destroy(); return result; } result = add_main_pipe_entry(main_pipe, &status, &entries[port_id][0]); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to add entry: %s", doca_error_get_descr(result)); stop_doca_flow_ports(nb_ports, ports); doca_flow_destroy(); return result; } result = doca_flow_pipe_queue_drain(ports[port_id], 0, NULL); if (result != DOCA_SUCCESS) { DOCA_LOG_ERR("Failed to process entries: %s", doca_error_get_descr(result)); stop_doca_flow_ports(nb_ports, ports); doca_flow_destroy(); return result; } if (status.nb_processed != num_of_entries || status.failure) { DOCA_LOG_ERR("Failed to process entries"); stop_doca_flow_ports(nb_ports, ports); doca_flow_destroy(); return DOCA_ERROR_BAD_STATE; } } /* Setup statistics context and wait for packets */ struct lpm_stats_context stats_ctx = {.nb_ports = nb_ports, .num_of_entries = num_of_entries, .entries = entries}; flow_wait_for_packets(15, print_lpm_stats_wrapper, &stats_ctx); result = stop_doca_flow_ports(nb_ports, ports); doca_flow_destroy(); return result; }