Search code examples
udpdpdk

DPDK Flow Rule Not Applied to UDP Traffic


The problem

I am trying to use a DPDK flow rule to route received UDP packets to different queues based on their UDP destination port. My flow passes validation when running rte_flow_validate(). However, the packets still all arrive in queue 0 on every device, regardless of destination port, as if the flow rules had not been configured. Does anyone have a clue as to why the flow rule might not be applied as expected?

The code

Below is the function that I wrote to configure this flow rule. Right now, this function is called immediately after configuring and starting my devices. (I have tried calling it elsewhere, but no luck. See troubleshooting section for details)

rte_flow * configure_udp_flow(uint16_t device_id, uint16_t udp_dst_port, uint16_t queue_id)
{
    rte_flow *flow = NULL;
    rte_flow_attr attr;
    rte_flow_item pattern[5];
    rte_flow_action action[5];
    rte_flow_action_queue queue;
    struct rte_flow_item_eth eth_spec;
    rte_flow_item_ipv4 ip_spec;
    rte_flow_item_udp udp_spec;
    rte_flow_item_udp udp_mask;

    memset(&attr, 0, sizeof(rte_flow_attr));
    memset(pattern, 0, sizeof(pattern));
    memset(action, 0, sizeof(action));
    memset(&queue, 0, sizeof(rte_flow_action_queue));
    memset(&eth_spec, 0, sizeof(rte_flow_item_eth));
    memset(&ip_spec, 0, sizeof(rte_flow_item_ipv4));
    memset(&udp_spec, 0, sizeof(rte_flow_item_udp));
    memset(&udp_mask, 0, sizeof(rte_flow_item_udp));

    attr.ingress = 1;

    // First level pattern, ETH, apply no actions
    pattern[0].type = RTE_FLOW_ITEM_TYPE_ETH;
    pattern[0].spec = &eth_spec;

    // Second level pattern, IP, apply no actions
    pattern[1].type = RTE_FLOW_ITEM_TYPE_IPV4;
    pattern[1].spec = &ip_spec;

    // Third level, UDP, match UDP destination port
    pattern[2].type = RTE_FLOW_ITEM_TYPE_UDP;
    udp_spec.hdr.dst_port = htons(udp_dst_port);
    pattern[2].spec = &udp_spec;
    udp_mask.hdr.dst_port = 0xffff;
    pattern[2].mask = &udp_mask;

    // Set pattern end
    pattern[3].type = RTE_FLOW_ITEM_TYPE_END;
    
    // Set action to move packet to queue
    action[0].type = RTE_FLOW_ACTION_TYPE_QUEUE;
    queue.index = queue_id;
    action[0].conf = &queue;
    action[1].type = RTE_FLOW_ACTION_TYPE_END;

    // Validate and create
    rte_flow_error flow_error;
    int res = rte_flow_validate(device_id, &attr, pattern, action, &flow_error);
    if (res < 0)
    {
        spdlog::error("Could not configure flow rule: {0}", flow_error.message);
        return flow;
    }
    
    flow = rte_flow_create(device_id, &attr, pattern, action, &flow_error);
    
    return flow;
}

Troubleshooting

I have tried the following:

  • Applyied various masks for the udp spec.
    • All 1s
    • All 0s
    • All 1s for destination port feild only, with remaining bits set to 0
    • All 0s for destination port feild only, with remaining bits set to 1
  • Moved the call to my function configure_udp_flow() to different parts of the configuration
    • At the beginning of the configuration for each device
    • At the end of configuration, right before I start each device
    • Immediately after I start each device
  • Set the port with different endian-ness
    • Network order
    • Host order
  • Intentionally configured an invalid flow rule to confirm that the validation step was working
    • The validation step worked and printed an error message to the console.
  • Verified that the incoming UDP packets have the expected destination port values in their headers

Solution

  • I fixed this problem by not setting ANY spec for the ETH and IPV4 levels.

    Previously, the pattern looked like this:

    // First level pattern, ETH, apply no actions
        pattern[0].type = RTE_FLOW_ITEM_TYPE_ETH;
        pattern[0].spec = &eth_spec;
    
        // Second level pattern, IP, apply no actions
        pattern[1].type = RTE_FLOW_ITEM_TYPE_IPV4;
        pattern[1].spec = &ip_spec;
    
        // Third level, UDP, match UDP destination port
        pattern[2].type = RTE_FLOW_ITEM_TYPE_UDP;
        udp_spec.hdr.dst_port = htons(udp_dst_port);
        pattern[2].spec = &udp_spec;
        udp_mask.hdr.dst_port = 0xffff;
        pattern[2].mask = &udp_mask;
    
        // Set pattern end
        pattern[3].type = RTE_FLOW_ITEM_TYPE_END;
    

    Now, the pattern looks like this, and works.

    // First level pattern, ETH, apply no actions
        pattern[0].type = RTE_FLOW_ITEM_TYPE_ETH;
    
        // Second level pattern, IP, apply no actions
        pattern[1].type = RTE_FLOW_ITEM_TYPE_IPV4;
    
        // Third level, UDP, match UDP destination port
        pattern[2].type = RTE_FLOW_ITEM_TYPE_UDP;
        udp_spec.hdr.dst_port = htons(udp_dst_port);
        pattern[2].spec = &udp_spec;
        udp_mask.hdr.dst_port = 0xffff;
        pattern[2].mask = &udp_mask;
    
        // Set pattern end
        pattern[3].type = RTE_FLOW_ITEM_TYPE_END;
    

    I assumed that assigning a zeroed-out spec for the ETH and IPV4 levels without specifying a mask would be the same as setting no spec at all. However, I was likely accidentally specifying a match to all zero values for ETH and IPV4 levels. I suspect I could have also fixed this problem by adding explicitly specifying a zeroed out masks for ETH and IPV4 levels as well, but have not verified it as there was no need.