From: Marat Khalili <qm2k21@gmail.com>
To: Konstantin Ananyev <konstantin.ananyev@huawei.com>,
Marat Khalili <qm2k21@gmail.com>,
Ferruh Yigit <ferruh.yigit@amd.com>
Cc: dev@dpdk.org, stephen@networkplumber.org, stable@dpdk.org
Subject: [PATCH 3/9] bpf: reject Tx callback installation with MT_LOCKFREE
Date: Sun, 11 Oct 2026 18:21:51 +0000 [thread overview]
Message-ID: <20261011182158.124246-4-qm2k21@gmail.com> (raw)
In-Reply-To: <20261011182158.124246-1-qm2k21@gmail.com>
The BPF ethdev packet callback synchronization mechanism expects only
one dataplane thread per port/queue pair at a time. When
RTE_ETH_TX_OFFLOAD_MT_LOCKFREE is enabled, multiple threads can invoke
rte_eth_tx_burst() concurrently on the same queue without locks. This
breaks the callback parity counter and can lead to use-after-free or
deadlocks during BPF program unload.
Reject BPF Tx callback installation with -ENOTSUP when
RTE_ETH_TX_OFFLOAD_MT_LOCKFREE is enabled on the port or queue.
Add a unit test in test_bpf verifying this rejection.
Bugzilla ID: 2051
Fixes: a93ff62a8938 ("bpf: introduce basic Rx/Tx filters")
Cc: stable@dpdk.org
Reported-by: Stephen Hemminger <stephen@networkplumber.org>
Signed-off-by: Marat Khalili <qm2k21@gmail.com>
---
app/test/test_bpf.c | 103 ++++++++++++++++++++++++++++++++++++++++++++
lib/bpf/bpf_pkt.c | 46 ++++++++++++++++++++
2 files changed, 149 insertions(+)
diff --git a/app/test/test_bpf.c b/app/test/test_bpf.c
index 3205afaa63..02523984b5 100644
--- a/app/test/test_bpf.c
+++ b/app/test/test_bpf.c
@@ -4617,6 +4617,107 @@ test_bpf_elf_rx_load(load_elf_image_t load_elf_image)
return ret == 0 ? TEST_SUCCESS : TEST_FAILED;
}
+static int
+null_vdev_mt_setup(const char *name, uint16_t *port, struct rte_mempool *pool)
+{
+ struct rte_eth_conf conf = {
+ .txmode = {
+ .offloads = RTE_ETH_TX_OFFLOAD_MT_LOCKFREE,
+ },
+ };
+ struct rte_eth_txconf txconf = { };
+ struct rte_eth_rxconf rxconf = { };
+ int ret;
+
+ ret = rte_vdev_init(name, NULL);
+ TEST_ASSERT(ret == 0, "rte_vdev_init(%s) failed: %d", name, ret);
+
+ ret = rte_eth_dev_get_port_by_name(name, port);
+ TEST_ASSERT(ret == 0, "failed to get port id for %s: %d", name, ret);
+
+ ret = rte_eth_dev_configure(*port, 1, 1, &conf);
+ TEST_ASSERT(ret == 0, "failed to configure port %u: %d", *port, ret);
+
+ ret = rte_eth_tx_queue_setup(*port, 0, BPF_TEST_BURST, SOCKET_ID_ANY, &txconf);
+ TEST_ASSERT(ret == 0, "failed to setup tx queue port %u: %d", *port, ret);
+
+ ret = rte_eth_rx_queue_setup(*port, 0, BPF_TEST_BURST, SOCKET_ID_ANY,
+ &rxconf, pool);
+ TEST_ASSERT(ret == 0, "failed to setup rx queue port %u: %d", *port, ret);
+
+ ret = rte_eth_dev_start(*port);
+ TEST_ASSERT(ret == 0, "failed to start port %u: %d", *port, ret);
+
+ return 0;
+}
+
+/* Test that installing a Tx filter is rejected when MT_LOCKFREE is enabled */
+static int
+test_bpf_elf_tx_mt_lockfree(load_elf_image_t load_elf_image)
+{
+ static const struct rte_bpf_arg prog_arg = {
+ .type = RTE_BPF_ARG_PTR,
+ .size = sizeof(struct dummy_net),
+ };
+ static const char null_dev[] = "net_null_bpf_mt";
+ struct rte_mempool *mb_pool = NULL;
+ struct rte_bpf *bpf = NULL;
+ uint16_t port = UINT16_MAX;
+ int ret;
+
+ mb_pool = rte_pktmbuf_pool_create("bpf_mt_test_pool", BPF_TEST_POOLSIZE,
+ 0, 0, RTE_MBUF_DEFAULT_BUF_SIZE,
+ SOCKET_ID_ANY);
+ TEST_ASSERT_NOT_NULL(mb_pool, "failed to create mempool");
+
+ ret = null_vdev_mt_setup(null_dev, &port, mb_pool);
+ if (ret != 0)
+ goto fail;
+
+ bpf = load_elf_image(app_test_bpf_filter_o, app_test_bpf_filter_o_len, "filter",
+ NULL, 0, &prog_arg);
+ if (bpf == NULL) {
+ printf("%s@%d: failed to load BPF filter image\n",
+ __func__, __LINE__);
+ ret = TEST_FAILED;
+ goto fail;
+ }
+
+ /* Tx installation must fail with -ENOTSUP due to MT_LOCKFREE */
+ ret = rte_bpf_eth_tx_install(port, 0, bpf, 0);
+ if (ret != -ENOTSUP) {
+ printf("%s@%d: expected rte_bpf_eth_tx_install to return -ENOTSUP, got %d\n",
+ __func__, __LINE__, ret);
+ ret = TEST_FAILED;
+ goto fail;
+ }
+
+ /* Rx installation on the same port must still succeed */
+ ret = rte_bpf_eth_rx_install(port, 0, bpf, 0);
+ if (ret != 0) {
+ printf("%s@%d: expected rte_bpf_eth_rx_install to succeed, got %d\n",
+ __func__, __LINE__, ret);
+ ret = TEST_FAILED;
+ goto fail;
+ }
+ rte_bpf_eth_rx_unload(port, 0);
+ /* bpf is destroyed by rte_bpf_eth_rx_unload */
+ bpf = NULL;
+
+ ret = TEST_SUCCESS;
+
+fail:
+ if (bpf != NULL)
+ rte_bpf_destroy(bpf);
+
+ if (port != UINT16_MAX) {
+ rte_eth_dev_stop(port);
+ rte_vdev_uninit(null_dev);
+ }
+
+ rte_mempool_free(mb_pool);
+ return ret;
+}
static int
test_bpf_elf(void)
@@ -4633,6 +4734,8 @@ test_bpf_elf(void)
ret = test_bpf_elf_load(elf_image_loaders[li]);
if (ret == TEST_SUCCESS)
ret = test_bpf_elf_tx_load(elf_image_loaders[li]);
+ if (ret == TEST_SUCCESS)
+ ret = test_bpf_elf_tx_mt_lockfree(elf_image_loaders[li]);
if (ret == TEST_SUCCESS)
ret = test_bpf_elf_rx_load(elf_image_loaders[li]);
}
diff --git a/lib/bpf/bpf_pkt.c b/lib/bpf/bpf_pkt.c
index 4223ef624e..eab85f7417 100644
--- a/lib/bpf/bpf_pkt.c
+++ b/lib/bpf/bpf_pkt.c
@@ -552,6 +552,48 @@ rte_bpf_eth_tx_unload(uint16_t port, uint16_t queue)
rte_spinlock_unlock(&cbh->lock);
}
+/*
+ * Check if the supplied port/queue pair is suitable for BPF callback execution.
+ */
+static int
+bpf_eth_check_mt(const struct bpf_eth_cbh *cbh, uint16_t port, uint16_t queue)
+{
+ struct rte_eth_txq_info qinfo;
+ struct rte_eth_conf conf;
+ int ret;
+
+ if (cbh->type == BPF_ETH_TX) {
+ ret = rte_eth_dev_conf_get(port, &conf);
+ if (ret != 0) {
+ RTE_BPF_LOG_LINE(ERR,
+ "%s(%u, %u): failed to get device configuration: %d;",
+ __func__, port, queue, ret);
+ return ret;
+ }
+
+ if ((conf.txmode.offloads & RTE_ETH_TX_OFFLOAD_MT_LOCKFREE) != 0) {
+ RTE_BPF_LOG_LINE(ERR,
+ "%s(%u, %u): MT_LOCKFREE Tx offload is enabled on port;",
+ __func__, port, queue);
+ return -ENOTSUP;
+ }
+
+ /*
+ * Ignore errors from tx_queue_info_get: the PMD might not implement
+ * txq_info_get (e.g. net/null), or the queue might not be set up yet.
+ */
+ if (rte_eth_tx_queue_info_get(port, queue, &qinfo) == 0 &&
+ (qinfo.conf.offloads & RTE_ETH_TX_OFFLOAD_MT_LOCKFREE) != 0) {
+ RTE_BPF_LOG_LINE(ERR,
+ "%s(%u, %u): MT_LOCKFREE Tx offload is enabled on queue;",
+ __func__, port, queue);
+ return -ENOTSUP;
+ }
+ }
+
+ return 0;
+}
+
static int
bpf_eth_elf_install(struct bpf_eth_cbh *cbh, uint16_t port, uint16_t queue,
struct rte_bpf *bpf, uint32_t flags)
@@ -572,6 +614,10 @@ bpf_eth_elf_install(struct bpf_eth_cbh *cbh, uint16_t port, uint16_t queue,
if (bpf->prm.nb_prog_arg != 1)
return -EINVAL;
+ rc = bpf_eth_check_mt(cbh, port, queue);
+ if (rc != 0)
+ return rc;
+
if (cbh->type == BPF_ETH_RX)
frx = select_rx_callback(bpf->prm.prog_arg[0].type, flags);
else
--
2.43.0
next prev parent reply other threads:[~2026-10-11 18:23 UTC|newest]
Thread overview: 32+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-11 18:21 [PATCH 0/9] bpf: clean up and document ethdev callback synchronization Marat Khalili
2026-10-11 18:21 ` [PATCH 1/9] bpf: document packet callback unload sequence Marat Khalili
2026-10-11 18:21 ` [PATCH 2/9] bpf: document ethdev callback threading limitations Marat Khalili
2026-10-11 18:21 ` Marat Khalili [this message]
2026-10-11 18:21 ` [PATCH 4/9] bpf: add callback flag to skip multi-thread check Marat Khalili
2026-10-11 19:48 ` Stephen Hemminger
2026-10-11 20:12 ` Marat Khalili
2026-10-11 18:21 ` [PATCH 5/9] bpf: remove redundant memory barrier on unload Marat Khalili
2026-10-11 19:50 ` Stephen Hemminger
2026-10-11 20:14 ` Marat Khalili
2026-10-11 18:21 ` [PATCH 6/9] bpf: simplify callback inuse tracking to boolean flag Marat Khalili
2026-10-11 19:51 ` Stephen Hemminger
2026-10-11 20:17 ` Marat Khalili
2026-10-11 18:21 ` [PATCH 7/9] bpf: reformat packet callback unload diagram Marat Khalili
2026-10-11 18:21 ` [PATCH 8/9] bpf: use relaxed stores for callback inuse tracking Marat Khalili
2026-10-11 19:52 ` Stephen Hemminger
2026-10-11 20:24 ` Marat Khalili
2026-10-11 18:21 ` [PATCH 9/9] bpf: make callback handle atomic with relaxed access Marat Khalili
2026-10-11 19:54 ` Stephen Hemminger
2026-10-11 20:31 ` Marat Khalili
2026-10-11 20:29 ` [RFC] bpf: fix callback unload with concurrent Tx burst Stephen Hemminger
2026-10-12 21:09 ` [PATCH v2 0/9] bpf: clean up and document ethdev callback synchronization Marat Khalili
2026-10-12 21:09 ` [PATCH v2 1/9] bpf: document packet callback unload sequence Marat Khalili
2026-10-12 21:09 ` [PATCH v2 2/9] bpf: document ethdev callback threading limitations Marat Khalili
2026-10-12 21:44 ` Stephen Hemminger
2026-10-12 21:09 ` [PATCH v2 3/9] bpf: reject Tx callback installation with MT_LOCKFREE Marat Khalili
2026-10-12 21:09 ` [PATCH v2 4/9] bpf: remove redundant memory barrier on unload Marat Khalili
2026-10-12 21:09 ` [PATCH v2 5/9] bpf: simplify callback inuse tracking to boolean flag Marat Khalili
2026-10-12 21:09 ` [PATCH v2 6/9] bpf: reformat packet callback unload diagram Marat Khalili
2026-10-12 21:09 ` [PATCH v2 7/9] bpf: use relaxed accesses for callback inuse tracking Marat Khalili
2026-10-12 21:09 ` [PATCH v2 8/9] bpf: make callback handle atomic with relaxed access Marat Khalili
2026-10-12 21:09 ` [PATCH v2 9/9] bpf: add callback flag to skip multi-thread check Marat Khalili
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261011182158.124246-4-qm2k21@gmail.com \
--to=qm2k21@gmail.com \
--cc=dev@dpdk.org \
--cc=ferruh.yigit@amd.com \
--cc=konstantin.ananyev@huawei.com \
--cc=stable@dpdk.org \
--cc=stephen@networkplumber.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.