This fix ensures that the queues of an AF_PACKET vdev do not
accidentally join an already existing fanout group in the same netns.
Joining such a group would make each member receive only a fraction of
the packets.

It sets the PACKET_FANOUT_FLAG_UNIQUEID flag for the first queue which
asks the kernel to allocate a new, unique fanout group ID, then asks the
kernel for that queue's fanout configuration which includes this
generated ID but not the flag anymore (the flag is ephemeral) and then
uses it for subsequent queues to make them join the same fanout group.

The bug exists since the PMD's introduction in 2014:

  # 2014 DPDK:
  $ git show 364e08f2bb:lib/librte_pmd_af_packet/rte_eth_af_packet.c |
      grep getpid
    fanout_arg = (getpid() ^ (*internals)->if_index) & 0xffff;

  # v26.07:
  $ git show v26.07:drivers/net/af_packet/rte_eth_af_packet.c |
      grep getpid
    return (getpid() ^ if_index) & 0xffff;

The chance for a collision might look low initially as it requires PIDs
to be equal mod 65536. But the chance is high for applications running
in containers having individual pidns but a shared netns, which can be
the case in a k8s pod, or also with manually started containers, e.g.
`--network ns:<path>` in Podman. The individual pidns increase the
chance of different processes having the same PID.

To quickly reproduce the issue, you can start multiple AF_PACKET DPDK
applications with qpairs greater than 1, prefixing them with
`unshare --fork -p` to have each one get the same PID as if they were
executed in a container. Each one then only receives a fraction of
the traffic.

The used UNIQUEID fanout flag is available since Linux v4.12, while DPDK
currently requires Linux v5.4 or newer. The change is also compatible
with RHEL 8.

Fixes: 364e08f2bbc0 ("af_packet: add PMD for AF_PACKET-based virtual devices")
Cc: [email protected]

Signed-off-by: Frank Dressler <[email protected]>
---
 drivers/net/af_packet/rte_eth_af_packet.c | 27 +++++++++++++++--------
 1 file changed, 18 insertions(+), 9 deletions(-)

diff --git a/drivers/net/af_packet/rte_eth_af_packet.c 
b/drivers/net/af_packet/rte_eth_af_packet.c
index 44f82c0309..562f0c4abe 100644
--- a/drivers/net/af_packet/rte_eth_af_packet.c
+++ b/drivers/net/af_packet/rte_eth_af_packet.c
@@ -839,12 +839,6 @@ open_packet_iface(const char *key __rte_unused,
 
 #define PACKET_FANOUT_INVALID -1
 
-static int
-get_fanout_group_id(int if_index)
-{
-       return (getpid() ^ if_index) & 0xffff;
-}
-
 static int
 get_fanout_mode(const char *fanout_mode)
 {
@@ -877,11 +871,11 @@ get_fanout_mode(const char *fanout_mode)
 }
 
 static int
-get_fanout(const char *fanout_mode, int if_index)
+get_fanout(const char *fanout_mode)
 {
        int load_balance = get_fanout_mode(fanout_mode);
        if (load_balance != PACKET_FANOUT_INVALID)
-               return get_fanout_group_id(if_index) | (load_balance << 16);
+               return load_balance << 16;
        else
                return PACKET_FANOUT_INVALID;
 }
@@ -996,11 +990,12 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
        sockaddr.sll_protocol = htons(ETH_P_ALL);
        sockaddr.sll_ifindex = (*internals)->if_index;
 
-       fanout_arg = get_fanout(fanout_mode, (*internals)->if_index);
+       fanout_arg = get_fanout(fanout_mode);
        if (fanout_arg == PACKET_FANOUT_INVALID) {
                PMD_LOG(ERR, "Invalid fanout mode: %s", fanout_mode);
                goto error;
        }
+       fanout_arg |= PACKET_FANOUT_FLAG_UNIQUEID << 16;
 
        /*
         * Set the flag opportunistically if multiple queues are requested.
@@ -1142,6 +1137,20 @@ rte_pmd_init_internals(struct rte_vdev_device *dev,
                                        name, pair->value);
                                goto error;
                        }
+
+                       /* read back the fanout arg so later queues join the 
same fanout group */
+                       if (q == 0) {
+                               socklen_t fanout_arg_len = sizeof(fanout_arg);
+                               rc = getsockopt(qsockfd, SOL_PACKET, 
PACKET_FANOUT, &fanout_arg,
+                                               &fanout_arg_len);
+                               if (rc == -1 || fanout_arg_len != 
sizeof(fanout_arg)) {
+                                       PMD_LOG_ERRNO(ERR,
+                                               "%s: could not get 
PACKET_FANOUT "
+                                               "on AF_PACKET socket for %s",
+                                               name, pair->value);
+                                       goto error;
+                               }
+                       }
                }
        }
 
-- 
2.56.0

Reply via email to