Skip to content

5.1 DPDK Flows

Mark Bednarczyk edited this page Nov 12, 2024 · 1 revision

Intel's Data Plane Development Kit (DPDK) is a set of libraries and drivers for fast packet processing, widely used in high-performance networking applications. One of its key features is the ability to filter packets efficiently, ensuring that only relevant traffic is captured and processed. This is typically achieved using the rte_flow API, which provides a flexible and powerful way to define filtering rules.

Overview of DPDK Packet Filtering with rte_flow

rte_flow allows developers to define flow rules that match specific packet patterns and specify actions to be taken when those patterns are detected. These rules can offload filtering tasks to the network hardware (if supported), reducing the processing burden on the CPU.

Key Components of rte_flow:

  1. Pattern: Defines the criteria to match packets (e.g., Ethernet type, IP addresses, ports).
  2. Action: Specifies what to do with matching packets (e.g., accept, drop, forward to a specific queue).
  3. Attributes: Additional parameters like queue selection or RSS hashing.

Filtering Syntax

While rte_flow doesn't use a traditional textual syntax like Berkeley Packet Filter (BPF), it provides a structured API to define patterns and actions. Below is an outline of how to construct these rules programmatically.

Basic Structure

A typical rte_flow rule consists of:

  1. Flow Item Array: Specifies the matching pattern.
  2. Action Array: Specifies the actions to perform on matched packets.
  3. Flow Attributes: Optional parameters for the flow.

Flow Items

Flow items represent the layers and fields to match in the packet. Common flow items include:

  • Ethernet (RTE_FLOW_ITEM_TYPE_ETH)
    • Matches Ethernet headers, allowing specification of MAC addresses and EtherTypes.
  • IPv4 (RTE_FLOW_ITEM_TYPE_IPV4)
    • Matches IPv4 headers, allowing specification of source/destination IPs, protocols, etc.
  • TCP (RTE_FLOW_ITEM_TYPE_TCP)
    • Matches TCP headers, allowing specification of source/destination ports.
  • UDP (RTE_FLOW_ITEM_TYPE_UDP)
    • Matches UDP headers, similar to TCP.

Actions

Actions define what to do with packets that match the pattern. Common actions include:

  • Queue (RTE_FLOW_ACTION_TYPE_QUEUE)
    • Directs packets to a specific receive queue.
  • Drop (RTE_FLOW_ACTION_TYPE_DROP)
    • Drops the packet.
  • Mark (RTE_FLOW_ACTION_TYPE_MARK)
    • Marks the packet with a user-defined value.
  • Count (RTE_FLOW_ACTION_TYPE_COUNT)
    • Counts the number of matching packets.

Examples of Filter Syntax

Example 1: Filter IPv4 TCP Packets on Port 80

Objective: Capture only IPv4 TCP packets destined for port 80 (HTTP).

struct rte_flow_item pattern_eth[] = {
    { 
        .type = RTE_FLOW_ITEM_TYPE_ETH,
        .spec = NULL, // Match any Ethernet
        .mask = NULL,
    },
    { 
        .type = RTE_FLOW_ITEM_TYPE_IPV4,
        .spec = &(struct rte_flow_item_ipv4){
            .hdr.src_addr = 0, // Match any source IP
            .hdr.dst_addr = 0, // Match any destination IP
            .hdr.next_proto_id = IPPROTO_TCP,
        },
        .mask = &(struct rte_flow_item_ipv4){
            .hdr.src_addr = 0,
            .hdr.dst_addr = 0,
            .hdr.next_proto_id = 0xFF,
        },
    },
    { 
        .type = RTE_FLOW_ITEM_TYPE_TCP,
        .spec = &(struct rte_flow_item_tcp){
            .hdr.dst_port = rte_cpu_to_be_16(80),
        },
        .mask = &(struct rte_flow_item_tcp){
            .hdr.dst_port = 0xFFFF,
        },
    },
    RTE_FLOW_ITEM_TYPE_END
};

struct rte_flow_action actions[] = {
    {
        .type = RTE_FLOW_ACTION_TYPE_QUEUE,
        .conf = &(struct rte_flow_action_queue){
            .index = 0, // Send to queue 0
        },
    },
    RTE_FLOW_ACTION_TYPE_END
};

struct rte_flow_attr attr = {
    .ingress = 1, // Apply to incoming packets
};

struct rte_flow *flow = rte_flow_create(port_id, &attr, pattern_eth, actions, NULL);
if (!flow) {
    rte_exit(EXIT_FAILURE, "Flow creation failed\n");
}

Example 2: Drop All UDP Traffic

Objective: Discard all incoming UDP packets.

struct rte_flow_item pattern_udp[] = {
    { 
        .type = RTE_FLOW_ITEM_TYPE_ETH,
        .spec = NULL,
        .mask = NULL,
    },
    { 
        .type = RTE_FLOW_ITEM_TYPE_IPV4,
        .spec = NULL,
        .mask = NULL,
    },
    { 
        .type = RTE_FLOW_ITEM_TYPE_UDP,
        .spec = NULL, // Match any UDP packet
        .mask = NULL,
    },
    RTE_FLOW_ITEM_TYPE_END
};

struct rte_flow_action actions[] = {
    {
        .type = RTE_FLOW_ACTION_TYPE_DROP, // Drop the packet
    },
    RTE_FLOW_ACTION_TYPE_END
};

struct rte_flow_attr attr = {
    .ingress = 1,
};

struct rte_flow *flow = rte_flow_create(port_id, &attr, pattern_udp, actions, NULL);
if (!flow) {
    rte_exit(EXIT_FAILURE, "Flow creation failed\n");
}

Example 3: Match IPv6 ICMPv6 Packets and Count Them

Objective: Count the number of IPv6 ICMPv6 packets.

struct rte_flow_item pattern_icmpv6[] = {
    { 
        .type = RTE_FLOW_ITEM_TYPE_ETH,
        .spec = NULL,
        .mask = NULL,
    },
    { 
        .type = RTE_FLOW_ITEM_TYPE_IPV6,
        .spec = &(struct rte_flow_item_ipv6){
            .hdr.proto = IPPROTO_ICMPV6,
        },
        .mask = &(struct rte_flow_item_ipv6){
            .hdr.proto = 0xFF,
        },
    },
    { 
        .type = RTE_FLOW_ITEM_TYPE_ICMPV6,
        .spec = NULL,
        .mask = NULL,
    },
    RTE_FLOW_ITEM_TYPE_END
};

struct rte_flow_action actions[] = {
    {
        .type = RTE_FLOW_ACTION_TYPE_COUNT,
        .conf = &(struct rte_flow_action_count){
            .shared = 0,
            .id = 0, // Identifier for the counter
        },
    },
    RTE_FLOW_ACTION_TYPE_END
};

struct rte_flow_attr attr = {
    .ingress = 1,
};

struct rte_flow *flow = rte_flow_create(port_id, &attr, pattern_icmpv6, actions, NULL);
if (!flow) {
    rte_exit(EXIT_FAILURE, "Flow creation failed\n");
}

Compiling and Applying Filters

Step 1: Initialize DPDK Environment

Before creating any flows, ensure that DPDK is initialized correctly, including EAL (Environment Abstraction Layer) initialization and port configuration.

int main(int argc, char **argv) {
    // Initialize the EAL
    int ret = rte_eal_init(argc, argv);
    if (ret < 0)
        rte_exit(EXIT_FAILURE, "Error with EAL initialization\n");

    // Configure and start the desired port
    // ...
}

Step 2: Define Flow Patterns and Actions

As shown in the examples above, define the pattern, actions, and attr structures to specify the filtering criteria and desired actions.

Step 3: Create the Flow

Use rte_flow_create to compile and apply the filter to a specific port.

struct rte_flow *flow = rte_flow_create(port_id, &attr, pattern, actions, NULL);
if (!flow) {
    rte_exit(EXIT_FAILURE, "Flow creation failed\n");
}
  • port_id: The identifier of the port to which the flow is applied.
  • attr: Flow attributes, such as ingress or egress direction.
  • pattern: Array of rte_flow_item defining the matching criteria.
  • actions: Array of rte_flow_action defining the actions for matched packets.
  • error: Optional parameter to receive detailed error information.

Step 4: Handle Flow Lifecycle

Manage the lifecycle of the flow, including deletion when no longer needed.

// To delete the flow
int delete_ret = rte_flow_destroy(port_id, flow, NULL);
if (delete_ret < 0) {
    rte_exit(EXIT_FAILURE, "Flow deletion failed\n");
}

Step 5: Start Packet Processing

Once flows are set up, begin processing packets. Only packets matching the defined flows will trigger the specified actions, effectively limiting the capture and processing to relevant traffic.

Best Practices

  1. Leverage Hardware Offloading: Utilize the NIC's flow capabilities to offload filtering tasks, reducing CPU load.
  2. Minimize Flow Rules: Keep the number of flow rules minimal to optimize performance.
  3. Use Shared Counters: When counting packets, use shared counters if possible to reduce resource usage.
  4. Error Handling: Always check the return values of rte_flow_create and handle errors appropriately.
  5. Performance Testing: Test the performance impact of flow rules to ensure they meet application requirements.

Conclusion

Intel's DPDK rte_flow API provides a robust and flexible mechanism for packet filtering, enabling high-performance applications to capture and process only the necessary traffic. By defining precise flow patterns and actions, developers can optimize packet processing, leverage hardware acceleration, and maintain efficient resource utilization.

For more detailed information, refer to the DPDK rte_flow Documentation and explore the various flow item types and actions supported by your network hardware.

Clone this wiki locally