* [PATCH 0/5] bpf enhancements
@ 2025-10-30 17:34 Stephen Hemminger
2025-10-30 17:34 ` [PATCH 1/5] bpf: add allocation annotations to functions Stephen Hemminger
` (6 more replies)
0 siblings, 7 replies; 14+ messages in thread
From: Stephen Hemminger @ 2025-10-30 17:34 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger
This is a series of fixes and tests for BPF functionality.
The new tests for Tx and Rx filtering require the infrastructure
to build a bpf program (clang) and the tool to convert object
file to header (xxd). If missing the test will get skipped.
Stephen Hemminger (5):
bpf: add allocation annotations to functions
bpf: use rte_pktmbuf_free_bulk
bpf: add a test for BPF ELF load
bpf: add test for rx and tx filtering
bpf: remove use of vla
app/test/bpf/meson.build | 53 ++++
app/test/bpf/test_bpf_filter.c | 35 +++
app/test/bpf/test_bpf_load.c | 60 +++++
app/test/meson.build | 2 +
app/test/test_bpf.c | 426 +++++++++++++++++++++++++++++++++
lib/bpf/bpf_pkt.c | 84 ++++---
lib/bpf/meson.build | 2 -
lib/bpf/rte_bpf.h | 11 +-
8 files changed, 632 insertions(+), 41 deletions(-)
create mode 100644 app/test/bpf/meson.build
create mode 100644 app/test/bpf/test_bpf_filter.c
create mode 100644 app/test/bpf/test_bpf_load.c
--
2.51.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH 1/5] bpf: add allocation annotations to functions
2025-10-30 17:34 [PATCH 0/5] bpf enhancements Stephen Hemminger
@ 2025-10-30 17:34 ` Stephen Hemminger
2025-10-30 17:34 ` [PATCH 2/5] bpf: use rte_pktmbuf_free_bulk Stephen Hemminger
` (5 subsequent siblings)
6 siblings, 0 replies; 14+ messages in thread
From: Stephen Hemminger @ 2025-10-30 17:34 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Konstantin Ananyev
In commit 80da7efbb4c4 ("eal: annotate allocation functions")
helper macros were added to provide compiler information to
detect misuse of alloc/free combinations. This covered many
of the functions in DPDK but missed the case of data allocated
by BPF load functions.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
lib/bpf/rte_bpf.h | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/lib/bpf/rte_bpf.h b/lib/bpf/rte_bpf.h
index 80ebb0210f..309d84bc51 100644
--- a/lib/bpf/rte_bpf.h
+++ b/lib/bpf/rte_bpf.h
@@ -18,6 +18,7 @@
#include <rte_common.h>
#include <rte_mbuf.h>
+#include <rte_malloc.h>
#include <bpf_def.h>
#ifdef __cplusplus
@@ -128,7 +129,8 @@ rte_bpf_destroy(struct rte_bpf *bpf);
* - ENOMEM - can't reserve enough memory
*/
struct rte_bpf *
-rte_bpf_load(const struct rte_bpf_prm *prm);
+rte_bpf_load(const struct rte_bpf_prm *prm)
+ __rte_malloc __rte_dealloc(rte_bpf_destroy, 1);
/**
* Create a new eBPF execution context and load BPF code from given ELF
@@ -152,7 +154,9 @@ rte_bpf_load(const struct rte_bpf_prm *prm);
*/
struct rte_bpf *
rte_bpf_elf_load(const struct rte_bpf_prm *prm, const char *fname,
- const char *sname);
+ const char *sname)
+ __rte_malloc __rte_dealloc(rte_bpf_destroy, 1);
+
/**
* Execute given BPF bytecode.
*
@@ -228,7 +232,8 @@ struct bpf_program;
* - ENOTSUP - operation not supported
*/
struct rte_bpf_prm *
-rte_bpf_convert(const struct bpf_program *prog);
+rte_bpf_convert(const struct bpf_program *prog)
+ __rte_malloc __rte_dealloc_free;
#ifdef __cplusplus
}
--
2.51.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH 2/5] bpf: use rte_pktmbuf_free_bulk
2025-10-30 17:34 [PATCH 0/5] bpf enhancements Stephen Hemminger
2025-10-30 17:34 ` [PATCH 1/5] bpf: add allocation annotations to functions Stephen Hemminger
@ 2025-10-30 17:34 ` Stephen Hemminger
2025-10-30 17:34 ` [PATCH 3/5] bpf: add a test for BPF ELF load Stephen Hemminger
` (4 subsequent siblings)
6 siblings, 0 replies; 14+ messages in thread
From: Stephen Hemminger @ 2025-10-30 17:34 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Konstantin Ananyev
When rte_pktmbuf_free_bulk is faster because it does single
mempool operation rather than per-packet.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
lib/bpf/bpf_pkt.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/lib/bpf/bpf_pkt.c b/lib/bpf/bpf_pkt.c
index 01f813c56b..087ac0f244 100644
--- a/lib/bpf/bpf_pkt.c
+++ b/lib/bpf/bpf_pkt.c
@@ -177,8 +177,7 @@ apply_filter(struct rte_mbuf *mb[], const uint64_t rc[], uint32_t num,
if (drop != 0) {
/* free filtered out mbufs */
- for (i = 0; i != k; i++)
- rte_pktmbuf_free(dr[i]);
+ rte_pktmbuf_free_bulk(dr, k);
} else {
/* copy filtered out mbufs beyond good ones */
for (i = 0; i != k; i++)
--
2.51.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH 3/5] bpf: add a test for BPF ELF load
2025-10-30 17:34 [PATCH 0/5] bpf enhancements Stephen Hemminger
2025-10-30 17:34 ` [PATCH 1/5] bpf: add allocation annotations to functions Stephen Hemminger
2025-10-30 17:34 ` [PATCH 2/5] bpf: use rte_pktmbuf_free_bulk Stephen Hemminger
@ 2025-10-30 17:34 ` Stephen Hemminger
2025-10-30 17:34 ` [PATCH 4/5] bpf: add test for rx and tx filtering Stephen Hemminger
` (3 subsequent siblings)
6 siblings, 0 replies; 14+ messages in thread
From: Stephen Hemminger @ 2025-10-30 17:34 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Konstantin Ananyev
Create an ELF file to load using clang.
Repackage the object into an array using xdd.
Write a test to see load and run the BPF.
If tools are not present, then the step is skipped.
Draft version made with Claude AI, but it didn't work.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
app/test/bpf/meson.build | 52 +++++++++++++
app/test/bpf/test_bpf_load.c | 60 ++++++++++++++
app/test/meson.build | 2 +
app/test/test_bpf.c | 146 +++++++++++++++++++++++++++++++++++
4 files changed, 260 insertions(+)
create mode 100644 app/test/bpf/meson.build
create mode 100644 app/test/bpf/test_bpf_load.c
diff --git a/app/test/bpf/meson.build b/app/test/bpf/meson.build
new file mode 100644
index 0000000000..2b944f5ea9
--- /dev/null
+++ b/app/test/bpf/meson.build
@@ -0,0 +1,52 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright 2025 Stephen Hemminger <stephen@networkplumber.org>
+
+bpf_test_hdrs = [ ]
+
+# use clang to compile to bpf
+clang_supports_bpf = false
+clang = find_program('clang', required: false)
+if clang.found()
+ clang_supports_bpf = run_command(clang, '-target', 'bpf', '--print-supported-cpus',
+ check: false).returncode() == 0
+endif
+
+if not clang_supports_bpf
+ message('app/test_bpf: no BPF load tests missing clang BPF support')
+ subdir_done()
+
+endif
+
+xxd = find_program('xxd', required: false)
+if not xxd.found()
+ message('app/test_bpf: missing xxd required to convert object to array')
+ subdir_done()
+endif
+
+# BPF compiler flags
+bpf_cflags = [ '-O2', '-target', 'bpf', '-g', '-c']
+
+# Enable test in test_bpf.c
+cflags += '-DTEST_BPF_ELF_LOAD'
+
+# BPF sources to compile
+test_bpf_progs = [
+ 'test_bpf_load'
+]
+
+foreach test_name : test_bpf_progs
+ # Compile BPF C source to object file
+ bpf_obj = custom_target(test_name + '_o',
+ input: test_name + '.c',
+ output: test_name + '.o',
+ command: [ clang, bpf_cflags, '@INPUT@', '-o', '@OUTPUT@'])
+
+ # Convert object file to C header using xxd
+ bpf_test_h = custom_target(test_name + '_h',
+ input: bpf_obj,
+ output: test_name + '.h',
+ command: [ xxd, '-i', '-n', test_name + '_data','@INPUT@', '@OUTPUT@'])
+
+ resources += bpf_test_h
+
+endforeach
diff --git a/app/test/bpf/test_bpf_load.c b/app/test/bpf/test_bpf_load.c
new file mode 100644
index 0000000000..42111f85ce
--- /dev/null
+++ b/app/test/bpf/test_bpf_load.c
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * BPF program for testing rte_bpf_elf_load
+ */
+
+#include <stdint.h>
+#include <stddef.h>
+
+/* Match the structures from test_bpf.c */
+struct dummy_offset {
+ uint64_t u64;
+ uint32_t u32;
+ uint16_t u16;
+ uint8_t u8;
+} __attribute__((packed));
+
+struct dummy_vect8 {
+ struct dummy_offset in[8];
+ struct dummy_offset out[8];
+};
+
+/* External function declaration - provided by test via xsym */
+extern void dummy_func1(const void *p, uint32_t *v32, uint64_t *v64);
+
+/*
+ * Test BPF function that will be loaded from ELF
+ * This function:
+ * 1. Reads values from input structure
+ * 2. Performs some computations
+ * 3. Writes results to output structure
+ * 4. Returns sum of values
+ */
+__attribute__((section("func"), used))
+uint64_t
+test_func(struct dummy_vect8 *arg)
+{
+ uint64_t sum = 0;
+ uint32_t v32;
+ uint64_t v64;
+
+ /* Load input values */
+ v32 = arg->in[0].u32;
+ v64 = arg->in[0].u64;
+
+ /* Call external function */
+ dummy_func1(arg, &v32, &v64);
+
+ /* Store results */
+ arg->out[0].u32 = v32;
+ arg->out[0].u64 = v64;
+
+ /* Calculate sum */
+ sum = arg->in[0].u64;
+ sum += arg->in[0].u32;
+ sum += arg->in[0].u16;
+ sum += arg->in[0].u8;
+ sum += v32;
+ sum += v64;
+
+ return sum;
+}
diff --git a/app/test/meson.build b/app/test/meson.build
index 8df8d3edd1..efec42a6bf 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -281,6 +281,8 @@ if not is_windows
install: false)
endif
+subdir('bpf')
+
subdir('test_cfgfiles')
resources += test_cfgfile_h
diff --git a/app/test/test_bpf.c b/app/test/test_bpf.c
index 90e10d7d2c..855fdc8ad1 100644
--- a/app/test/test_bpf.c
+++ b/app/test/test_bpf.c
@@ -6,6 +6,7 @@
#include <string.h>
#include <stdint.h>
#include <inttypes.h>
+#include <unistd.h>
#include <rte_memory.h>
#include <rte_debug.h>
@@ -14,6 +15,8 @@
#include <rte_random.h>
#include <rte_byteorder.h>
#include <rte_errno.h>
+#include <rte_bpf.h>
+
#include "test.h"
#if !defined(RTE_LIB_BPF)
@@ -3278,6 +3281,149 @@ test_bpf(void)
REGISTER_FAST_TEST(bpf_autotest, true, true, test_bpf);
+#ifdef TEST_BPF_ELF_LOAD
+
+/*
+ * Helper function to write BPF object data to temporary file.
+ * Returns temp file path on success, NULL on failure.
+ * Caller must free the returned path and unlink the file.
+ */
+static char *
+create_temp_bpf_file(const uint8_t *data, size_t size, const char *suffix)
+{
+ char *tmpfile = NULL;
+ int fd;
+ ssize_t written;
+
+ if (asprintf(&tmpfile, "/tmp/dpdk_bpf_%s_XXXXXX", suffix) < 0) {
+ printf("%s@%d: asprintf failed: %s\n",
+ __func__, __LINE__, strerror(errno));
+ return NULL;
+ }
+
+ /* Create and open temp file */
+ fd = mkstemp(tmpfile);
+ if (fd < 0) {
+ printf("%s@%d: mkstemp(%s) failed: %s\n",
+ __func__, __LINE__, tmpfile, strerror(errno));
+ free(tmpfile);
+ return NULL;
+ }
+
+ /* Write BPF object data */
+ written = write(fd, data, size);
+ close(fd);
+
+ if (written != (ssize_t)size) {
+ printf("%s@%d: write failed: %s\n",
+ __func__, __LINE__, strerror(errno));
+ unlink(tmpfile);
+ free(tmpfile);
+ return NULL;
+ }
+
+ return tmpfile;
+}
+
+#include "test_bpf_load.h"
+
+static int
+test_bpf_elf_load(void)
+{
+ uint8_t tbuf[sizeof(struct dummy_vect8)];
+ const struct rte_bpf_xsym xsym[] = {
+ {
+ .name = RTE_STR(dummy_func1),
+ .type = RTE_BPF_XTYPE_FUNC,
+ .func = {
+ .val = (void *)dummy_func1,
+ .nb_args = 3,
+ .args = {
+ [0] = {
+ .type = RTE_BPF_ARG_PTR,
+ .size = sizeof(struct dummy_offset),
+ },
+ [1] = {
+ .type = RTE_BPF_ARG_PTR,
+ .size = sizeof(uint32_t),
+ },
+ [2] = {
+ .type = RTE_BPF_ARG_PTR,
+ .size = sizeof(uint64_t),
+ },
+ },
+ },
+ },
+ };
+ int ret;
+
+ /* Create temp file from embedded BPF object */
+ char *tmpfile = create_temp_bpf_file(test_bpf_load_data,
+ test_bpf_load_data_len, "load");
+ if (tmpfile == NULL)
+ return -1;
+
+ /* Try to load BPF program from temp file */
+ const struct rte_bpf_prm prm = {
+ .xsym = xsym,
+ .nb_xsym = RTE_DIM(xsym),
+ .prog_arg = {
+ .type = RTE_BPF_ARG_PTR,
+ .size = sizeof(tbuf),
+ },
+ };
+ struct rte_bpf *bpf = rte_bpf_elf_load(&prm, tmpfile, "func");
+ TEST_ASSERT(bpf != NULL, "failed to load BPF from %s %d:%s",
+ tmpfile, rte_errno, strerror(rte_errno));
+
+ /* Prepare test data */
+ struct dummy_vect8 *dv = (struct dummy_vect8 *)tbuf;
+ memset(dv, 0, sizeof(*dv));
+ dv->in[0].u64 = (int32_t)TEST_FILL_1;
+ dv->in[0].u32 = dv->in[0].u64;
+ dv->in[0].u16 = dv->in[0].u64;
+ dv->in[0].u8 = dv->in[0].u64;
+
+ /* Execute loaded BPF program */
+ uint64_t sum = rte_bpf_exec(bpf, tbuf);
+ TEST_ASSERT(sum != 0, "BPF execution returned: %" PRIu64, sum);
+
+ /* Test JIT if available */
+ struct rte_bpf_jit jit;
+ ret = rte_bpf_get_jit(bpf, &jit);
+ TEST_ASSERT(ret == 0, "rte_bpf_get_jit failed: %d", ret);
+
+ if (jit.func != NULL) {
+ memset(dv, 0, sizeof(*dv));
+ dv->in[0].u64 = (int32_t)TEST_FILL_1;
+ dv->in[0].u32 = dv->in[0].u64;
+ dv->in[0].u16 = dv->in[0].u64;
+ dv->in[0].u8 = dv->in[0].u64;
+
+ uint64_t jsum = jit.func(tbuf);
+ TEST_ASSERT_EQUAL(sum, jsum, "BPF JIT execution difference");
+ }
+
+ rte_bpf_destroy(bpf);
+ unlink(tmpfile);
+ free(tmpfile);
+
+ printf("%s: ELF load test passed\n", __func__);
+ return TEST_SUCCESS;
+}
+#else
+
+static int
+test_bpf_elf_load(void)
+{
+ printf("BPF compile not supported, skipping test\n");
+ return TEST_SKIPPED;
+}
+
+#endif /* !TEST_BPF_ELF_LOAD */
+
+REGISTER_FAST_TEST(bpf_elf_load_autotest, true, true, test_bpf_elf_load);
+
#ifndef RTE_HAS_LIBPCAP
static int
--
2.51.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH 4/5] bpf: add test for rx and tx filtering
2025-10-30 17:34 [PATCH 0/5] bpf enhancements Stephen Hemminger
` (2 preceding siblings ...)
2025-10-30 17:34 ` [PATCH 3/5] bpf: add a test for BPF ELF load Stephen Hemminger
@ 2025-10-30 17:34 ` Stephen Hemminger
2025-10-30 17:34 ` [PATCH 5/5] bpf: remove use of vla Stephen Hemminger
` (2 subsequent siblings)
6 siblings, 0 replies; 14+ messages in thread
From: Stephen Hemminger @ 2025-10-30 17:34 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Konstantin Ananyev
New test using null device to test filtering with BPF.
Initial not working version build with Claude AI.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
app/test/bpf/meson.build | 3 +-
app/test/bpf/test_bpf_filter.c | 35 +++++
app/test/test_bpf.c | 280 +++++++++++++++++++++++++++++++++
3 files changed, 317 insertions(+), 1 deletion(-)
create mode 100644 app/test/bpf/test_bpf_filter.c
diff --git a/app/test/bpf/meson.build b/app/test/bpf/meson.build
index 2b944f5ea9..be7c643b41 100644
--- a/app/test/bpf/meson.build
+++ b/app/test/bpf/meson.build
@@ -31,7 +31,8 @@ cflags += '-DTEST_BPF_ELF_LOAD'
# BPF sources to compile
test_bpf_progs = [
- 'test_bpf_load'
+ 'test_bpf_load',
+ 'test_bpf_filter',
]
foreach test_name : test_bpf_progs
diff --git a/app/test/bpf/test_bpf_filter.c b/app/test/bpf/test_bpf_filter.c
new file mode 100644
index 0000000000..f2945b7c73
--- /dev/null
+++ b/app/test/bpf/test_bpf_filter.c
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * BPF TX filter program for testing rte_bpf_eth_tx_elf_load
+ */
+
+#include <stdint.h>
+#include <stddef.h>
+
+/*
+ * Simple TX filter that accepts TCP packets
+ *
+ * BPF TX programs receive pointer to data and should return:
+ * 0 = drop packet
+ * non-zero = rx/tx packet
+ *
+ * This filter checks:
+ * 1. Packet is IPv4
+ * 2. Protocol is TCP (IPPROTO_TCP = 6)
+ */
+__attribute__((section("filter"), used))
+uint64_t
+test_filter(void *pkt)
+{
+ uint8_t *data = pkt;
+
+ /* Read version and IHL (first byte of IP header) */
+ uint8_t version_ihl = data[14];
+
+ /* Check IPv4 version (upper 4 bits should be 4) */
+ if ((version_ihl >> 4) != 4)
+ return 0;
+
+ /* Protocol field (byte 9 of IP header) must be TCP (6) */
+ uint8_t proto = data[14 + 9];
+ return (proto == 6);
+}
diff --git a/app/test/test_bpf.c b/app/test/test_bpf.c
index 855fdc8ad1..ce537dacf2 100644
--- a/app/test/test_bpf.c
+++ b/app/test/test_bpf.c
@@ -16,6 +16,12 @@
#include <rte_byteorder.h>
#include <rte_errno.h>
#include <rte_bpf.h>
+#include <rte_ethdev.h>
+#include <rte_bpf_ethdev.h>
+#include <rte_bus_vdev.h>
+#include <rte_ether.h>
+#include <rte_ip.h>
+#include <rte_tcp.h>
#include "test.h"
@@ -3411,6 +3417,264 @@ test_bpf_elf_load(void)
printf("%s: ELF load test passed\n", __func__);
return TEST_SUCCESS;
}
+
+#include "test_bpf_filter.h"
+
+#define BPF_TEST_BURST 128u
+#define BPF_TEST_PKT_LEN 100u
+
+static int null_vdev_setup(const char *name, uint16_t *port, struct rte_mempool *pool)
+{
+ int ret;
+
+ /* Make a null device */
+ 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);
+
+ struct rte_eth_conf conf = { };
+ ret = rte_eth_dev_configure(*port, 1, 1, &conf);
+ TEST_ASSERT(ret == 0, "failed to configure port %u: %d", *port, ret);
+
+ struct rte_eth_txconf txconf = { };
+ 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);
+
+ struct rte_eth_rxconf rxconf = { };
+ 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;
+}
+
+static unsigned int
+setup_mbufs(struct rte_mbuf *burst[], unsigned int n)
+{
+ struct rte_ether_hdr eh = {
+ .ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4),
+ };
+ const struct rte_ipv4_hdr iph = {
+ .version_ihl = RTE_IPV4_VHL_DEF,
+ .total_length = rte_cpu_to_be_16(BPF_TEST_PKT_LEN - sizeof(eh)),
+ .time_to_live = IPDEFTTL,
+ .src_addr = rte_cpu_to_be_32(ip_src_addr),
+ .dst_addr = rte_cpu_to_be_32(ip_dst_addr),
+ };
+ unsigned int tcp_count = 0;
+
+ rte_eth_random_addr(eh.dst_addr.addr_bytes);
+
+ for (unsigned int i = 0; i < n; i++) {
+ struct rte_mbuf *mb = burst[i];
+
+ /* Setup Ethernet header */
+ *rte_pktmbuf_mtod(mb, struct rte_ether_hdr *) = eh;
+
+ /* Setup IP header */
+ struct rte_ipv4_hdr *ip
+ = rte_pktmbuf_mtod_offset(mb, struct rte_ipv4_hdr *, sizeof(eh));
+ *ip = iph;
+
+ if (rte_rand() & 1) {
+ struct rte_udp_hdr *udp
+ = rte_pktmbuf_mtod_offset(mb, struct rte_udp_hdr *,
+ sizeof(eh) + sizeof(iph));
+
+ ip->next_proto_id = IPPROTO_UDP;
+ *udp = (struct rte_udp_hdr) {
+ .src_port = rte_cpu_to_be_16(9), /* discard */
+ .dst_port = rte_cpu_to_be_16(9), /* discard */
+ .dgram_len = BPF_TEST_PKT_LEN - sizeof(eh) - sizeof(iph),
+ };
+
+ } else {
+ struct rte_tcp_hdr *tcp
+ = rte_pktmbuf_mtod_offset(mb, struct rte_tcp_hdr *,
+ sizeof(eh) + sizeof(iph));
+
+ ip->next_proto_id = IPPROTO_TCP;
+ *tcp = (struct rte_tcp_hdr) {
+ .src_port = rte_cpu_to_be_16(9), /* discard */
+ .dst_port = rte_cpu_to_be_16(9), /* discard */
+ .tcp_flags = RTE_TCP_RST_FLAG,
+ };
+ ++tcp_count;
+ }
+ }
+
+ return tcp_count;
+}
+
+static int bpf_tx_test(uint16_t port, const char *tmpfile, struct rte_mempool *pool,
+ const char *fname, uint32_t flags)
+{
+ const struct rte_bpf_prm prm = {
+ .prog_arg = {
+ .type = RTE_BPF_ARG_PTR,
+ .size = sizeof(struct rte_mbuf),
+ },
+ };
+ int ret;
+
+ unsigned int before = rte_mempool_avail_count(pool);
+
+ struct rte_mbuf *pkts[BPF_TEST_BURST] = { };
+ ret = rte_pktmbuf_alloc_bulk(pool, pkts, BPF_TEST_BURST);
+ TEST_ASSERT(ret == 0, "failed to allocate mbufs");
+
+ uint16_t expect = setup_mbufs(pkts, BPF_TEST_BURST);
+
+ /* Try to load BPF TX program from temp file */
+ ret = rte_bpf_eth_tx_elf_load(port, 0, &prm, tmpfile, fname, flags);
+ TEST_ASSERT(ret == 0, "failed to load BPF filter from temp file %s: %d",
+ tmpfile, ret);
+
+ uint16_t sent = rte_eth_tx_burst(port, 0, pkts, BPF_TEST_BURST);
+ TEST_ASSERT_EQUAL(sent, expect, "rte_eth_tx_burst returned: %u expected %u",
+ sent, expect);
+
+ /* The unsent packets should be dropped */
+ rte_pktmbuf_free_bulk(pkts + sent, BPF_TEST_BURST - sent);
+
+ /* Pool should have same number of packets avail */
+ unsigned int after = rte_mempool_avail_count(pool);
+ TEST_ASSERT_EQUAL(before, after, "Mempool available %u != %u leaks?", before, after);
+
+ rte_bpf_eth_tx_unload(port, 0);
+ return TEST_SUCCESS;
+}
+
+static int
+test_bpf_elf_tx_load(void)
+{
+ const char null_dev[] = "net_null_bpf0";
+ struct rte_mempool *mb_pool = NULL;
+ uint16_t port = UINT16_MAX;
+ int ret;
+
+ printf("%s start\n", __func__);
+
+ /* Make a pool for packets */
+ mb_pool = rte_pktmbuf_pool_create("bpf_tx_test_pool", 2 * BPF_TEST_BURST,
+ 0, 0, RTE_MBUF_DEFAULT_BUF_SIZE,
+ SOCKET_ID_ANY);
+ TEST_ASSERT(mb_pool != NULL, "failed to create mempool");
+
+ ret = null_vdev_setup(null_dev, &port, mb_pool);
+ if (ret != 0)
+ goto fail;
+
+ /* Create temp file from embedded BPF object */
+ char *tmpfile = create_temp_bpf_file(test_bpf_filter_data,
+ test_bpf_filter_data_len, "tx");
+ if (tmpfile == NULL)
+ goto fail;
+
+ /* Do test with VM */
+ ret = bpf_tx_test(port, tmpfile, mb_pool, "filter", 0);
+ if (ret != 0)
+ goto fail;
+
+ /* Repeat with JIT */
+ ret = bpf_tx_test(port, tmpfile, mb_pool, "filter", RTE_BPF_ETH_F_JIT);
+ if (ret == 0)
+ printf("%s: TX ELF load test passed\n", __func__);
+
+fail:
+ if (tmpfile) {
+ unlink(tmpfile);
+ free(tmpfile);
+ }
+
+ if (port != UINT16_MAX)
+ rte_vdev_uninit(null_dev);
+
+ rte_mempool_free(mb_pool);
+
+ return ret == 0 ? TEST_SUCCESS : TEST_FAILED;
+}
+
+static int bpf_rx_test(uint16_t port, const char *tmpfile, uint32_t flags)
+{
+ struct rte_mbuf *pkts[BPF_TEST_BURST];
+ const struct rte_bpf_prm prm = {
+ .prog_arg = {
+ .type = RTE_BPF_ARG_PTR,
+ .size = sizeof(struct rte_mbuf),
+ },
+ };
+ int ret;
+
+ /* Load BPF program to drop all packets */
+ ret = rte_bpf_eth_rx_elf_load(port, 0, &prm, tmpfile, "filter", flags);
+ TEST_ASSERT(ret == 0, "failed to load BPF filter from temp file %s: %d",
+ tmpfile, ret);
+
+ uint16_t rcvd = rte_eth_rx_burst(port, 0, pkts, BPF_TEST_BURST);
+ TEST_ASSERT(rcvd == 0, "rte_eth_rx_burst returned: %u", rcvd);
+
+ rte_bpf_eth_rx_unload(port, 0);
+
+ return TEST_SUCCESS;
+}
+
+static int
+test_bpf_elf_rx_load(void)
+{
+ const char null_dev[] = "net_null_bpf0";
+ struct rte_mempool *mb_pool = NULL;
+ uint16_t port;
+ int ret;
+
+ printf("%s start\n", __func__);
+
+ /* Make a pool for packets */
+ mb_pool = rte_pktmbuf_pool_create("bpf_rx_test_pool", 2 * BPF_TEST_BURST,
+ 0, 0, RTE_MBUF_DEFAULT_BUF_SIZE,
+ SOCKET_ID_ANY);
+ TEST_ASSERT(mb_pool != NULL, "failed to create mempool");
+
+ ret = null_vdev_setup(null_dev, &port, mb_pool);
+ if (ret != 0)
+ goto fail;
+
+ /* Create temp file from embedded BPF object */
+ char *tmpfile = create_temp_bpf_file(test_bpf_filter_data,
+ test_bpf_filter_data_len, "tx");
+ if (tmpfile == NULL)
+ goto fail;
+
+ /* Do test with VM */
+ ret = bpf_rx_test(port, tmpfile, 0);
+ if (ret != 0)
+ goto fail;
+
+ /* Repeat with JIT */
+ ret = bpf_rx_test(port, tmpfile, RTE_BPF_ETH_F_JIT);
+ if (ret != 0)
+ goto fail;
+
+ printf("%s: RX ELF load test passed\n", __func__);
+
+fail:
+ if (tmpfile) {
+ unlink(tmpfile);
+ free(tmpfile);
+ }
+
+ if (port != UINT16_MAX)
+ rte_vdev_uninit(null_dev);
+
+ rte_mempool_free(mb_pool);
+
+ return ret == 0 ? TEST_SUCCESS : TEST_FAILED;
+}
#else
static int
@@ -3420,9 +3684,25 @@ test_bpf_elf_load(void)
return TEST_SKIPPED;
}
+static int
+test_bpf_elf_tx_load(void)
+{
+ printf("BPF compile not supported, skipping Tx test\n");
+ return TEST_SKIPPED;
+}
+
+static int
+test_bpf_elf_rx_load(void)
+{
+ printf("BPF compile not supported, skipping Tx test\n");
+ return TEST_SKIPPED;
+}
+
#endif /* !TEST_BPF_ELF_LOAD */
REGISTER_FAST_TEST(bpf_elf_load_autotest, true, true, test_bpf_elf_load);
+REGISTER_FAST_TEST(bpf_eth_tx_elf_load_autotest, true, true, test_bpf_elf_tx_load);
+REGISTER_FAST_TEST(bpf_eth_rx_elf_load_autotest, true, true, test_bpf_elf_rx_load);
#ifndef RTE_HAS_LIBPCAP
--
2.51.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH 5/5] bpf: remove use of vla
2025-10-30 17:34 [PATCH 0/5] bpf enhancements Stephen Hemminger
` (3 preceding siblings ...)
2025-10-30 17:34 ` [PATCH 4/5] bpf: add test for rx and tx filtering Stephen Hemminger
@ 2025-10-30 17:34 ` Stephen Hemminger
2025-10-31 11:39 ` [PATCH 0/5] bpf enhancements Marat Khalili
2025-10-31 16:41 ` [PATCH v2 0/5] BPF enhancements Stephen Hemminger
6 siblings, 0 replies; 14+ messages in thread
From: Stephen Hemminger @ 2025-10-30 17:34 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Konstantin Ananyev
Replace variable length array with two part loop which
applies filter in bursts up to 32 packets.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
lib/bpf/bpf_pkt.c | 81 ++++++++++++++++++++++++++-------------------
lib/bpf/meson.build | 2 --
2 files changed, 47 insertions(+), 36 deletions(-)
diff --git a/lib/bpf/bpf_pkt.c b/lib/bpf/bpf_pkt.c
index 087ac0f244..15d36739a0 100644
--- a/lib/bpf/bpf_pkt.c
+++ b/lib/bpf/bpf_pkt.c
@@ -157,13 +157,16 @@ bpf_eth_cbh_add(struct bpf_eth_cbh *cbh, uint16_t port, uint16_t queue)
/*
* BPF packet processing routines.
*/
+#define BPF_FILTER_BURST 32u
static inline uint32_t
apply_filter(struct rte_mbuf *mb[], const uint64_t rc[], uint32_t num,
uint32_t drop)
{
uint32_t i, j, k;
- struct rte_mbuf *dr[num];
+ struct rte_mbuf *dr[BPF_FILTER_BURST];
+
+ RTE_ASSERT(num <= BPF_FILTER_BURST);
for (i = 0, j = 0, k = 0; i != num; i++) {
@@ -191,65 +194,75 @@ static inline uint32_t
pkt_filter_vm(const struct rte_bpf *bpf, struct rte_mbuf *mb[], uint32_t num,
uint32_t drop)
{
- uint32_t i;
- void *dp[num];
- uint64_t rc[num];
+ uint32_t matched = 0;
+
+ for (uint32_t i = 0; i < num; i += BPF_FILTER_BURST) {
+ uint32_t burst_sz = RTE_MIN(num, BPF_FILTER_BURST);
+ void *dp[BPF_FILTER_BURST];
+ uint64_t rc[BPF_FILTER_BURST];
- for (i = 0; i != num; i++)
- dp[i] = rte_pktmbuf_mtod(mb[i], void *);
+ for (uint32_t j = 0; j < burst_sz; j++)
+ dp[j] = rte_pktmbuf_mtod(mb[i + j], void *);
- rte_bpf_exec_burst(bpf, dp, rc, num);
- return apply_filter(mb, rc, num, drop);
+ rte_bpf_exec_burst(bpf, dp, rc, burst_sz);
+ matched += apply_filter(mb + i, rc, burst_sz, drop);
+ }
+ return matched;
}
static inline uint32_t
pkt_filter_jit(const struct rte_bpf_jit *jit, struct rte_mbuf *mb[],
uint32_t num, uint32_t drop)
{
- uint32_t i, n;
- void *dp;
- uint64_t rc[num];
-
- n = 0;
- for (i = 0; i != num; i++) {
- dp = rte_pktmbuf_mtod(mb[i], void *);
- rc[i] = jit->func(dp);
- n += (rc[i] == 0);
- }
+ uint32_t matched = 0;
+
+ for (uint32_t i = 0; i < num; i += BPF_FILTER_BURST) {
+ uint32_t burst_sz = RTE_MIN(num, BPF_FILTER_BURST);
+ uint64_t rc[BPF_FILTER_BURST];
+
+ for (uint32_t j = 0; j < burst_sz; j++) {
+ void *dp = rte_pktmbuf_mtod(mb[i + j], void *);
- if (n != 0)
- num = apply_filter(mb, rc, num, drop);
+ rc[j] = jit->func(dp);
+ }
- return num;
+ matched += apply_filter(mb + i, rc, burst_sz, drop);
+ }
+ return matched;
}
static inline uint32_t
pkt_filter_mb_vm(const struct rte_bpf *bpf, struct rte_mbuf *mb[], uint32_t num,
uint32_t drop)
{
- uint64_t rc[num];
+ uint32_t matched = 0;
+
+ for (uint32_t i = 0; i < num; i += BPF_FILTER_BURST) {
+ uint32_t burst_sz = RTE_MIN(num, BPF_FILTER_BURST);
+ uint64_t rc[BPF_FILTER_BURST];
- rte_bpf_exec_burst(bpf, (void **)mb, rc, num);
- return apply_filter(mb, rc, num, drop);
+ rte_bpf_exec_burst(bpf, (void **)mb, rc, burst_sz);
+ matched += apply_filter(mb + i, rc, burst_sz, drop);
+ }
+ return matched;
}
static inline uint32_t
pkt_filter_mb_jit(const struct rte_bpf_jit *jit, struct rte_mbuf *mb[],
uint32_t num, uint32_t drop)
{
- uint32_t i, n;
- uint64_t rc[num];
+ uint32_t matched = 0;
- n = 0;
- for (i = 0; i != num; i++) {
- rc[i] = jit->func(mb[i]);
- n += (rc[i] == 0);
- }
+ for (uint32_t i = 0; i < num; i += BPF_FILTER_BURST) {
+ uint32_t burst_sz = RTE_MIN(num, BPF_FILTER_BURST);
+ uint64_t rc[BPF_FILTER_BURST];
- if (n != 0)
- num = apply_filter(mb, rc, num, drop);
+ for (uint32_t j = 0; j < burst_sz; j++)
+ rc[j] = jit->func(mb[i + j]);
- return num;
+ matched += apply_filter(mb + i, rc, burst_sz, drop);
+ }
+ return matched;
}
/*
diff --git a/lib/bpf/meson.build b/lib/bpf/meson.build
index 28df7f469a..aa258a9061 100644
--- a/lib/bpf/meson.build
+++ b/lib/bpf/meson.build
@@ -7,8 +7,6 @@ if is_windows
subdir_done()
endif
-cflags += no_wvla_cflag
-
if arch_subdir == 'x86' and dpdk_conf.get('RTE_ARCH_32')
build = false
reason = 'not supported on 32-bit x86'
--
2.51.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* RE: [PATCH 0/5] bpf enhancements
2025-10-30 17:34 [PATCH 0/5] bpf enhancements Stephen Hemminger
` (4 preceding siblings ...)
2025-10-30 17:34 ` [PATCH 5/5] bpf: remove use of vla Stephen Hemminger
@ 2025-10-31 11:39 ` Marat Khalili
2025-10-31 16:37 ` Stephen Hemminger
2025-10-31 16:41 ` [PATCH v2 0/5] BPF enhancements Stephen Hemminger
6 siblings, 1 reply; 14+ messages in thread
From: Marat Khalili @ 2025-10-31 11:39 UTC (permalink / raw)
To: Stephen Hemminger; +Cc: dev
> -----Original Message-----
> From: Stephen Hemminger <stephen@networkplumber.org>
> Sent: Thursday 30 October 2025 17:34
> To: dev@dpdk.org
> Cc: Stephen Hemminger <stephen@networkplumber.org>
> Subject: [PATCH 0/5] bpf enhancements
>
> This is a series of fixes and tests for BPF functionality.
> The new tests for Tx and Rx filtering require the infrastructure
> to build a bpf program (clang) and the tool to convert object
> file to header (xxd). If missing the test will get skipped.
Thank you for doing this, I will try to have a look next week.
About the overall approach to getting the BPF object file, I understand the clang part, but do we really need to convert object files to headers and then write them back? Could we just pass these object files around as test resources instead? Not that xxd was not present in most systems, but looks like extra work and complexity.
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 0/5] bpf enhancements
2025-10-31 11:39 ` [PATCH 0/5] bpf enhancements Marat Khalili
@ 2025-10-31 16:37 ` Stephen Hemminger
0 siblings, 0 replies; 14+ messages in thread
From: Stephen Hemminger @ 2025-10-31 16:37 UTC (permalink / raw)
To: Marat Khalili; +Cc: dev
On Fri, 31 Oct 2025 11:39:54 +0000
Marat Khalili <marat.khalili@huawei.com> wrote:
> > -----Original Message-----
> > From: Stephen Hemminger <stephen@networkplumber.org>
> > Sent: Thursday 30 October 2025 17:34
> > To: dev@dpdk.org
> > Cc: Stephen Hemminger <stephen@networkplumber.org>
> > Subject: [PATCH 0/5] bpf enhancements
> >
> > This is a series of fixes and tests for BPF functionality.
> > The new tests for Tx and Rx filtering require the infrastructure
> > to build a bpf program (clang) and the tool to convert object
> > file to header (xxd). If missing the test will get skipped.
>
> Thank you for doing this, I will try to have a look next week.
>
> About the overall approach to getting the BPF object file, I understand the clang part, but do we really need to convert object files to headers and then write them back? Could we just pass these object files around as test resources instead? Not that xxd was not present in most systems, but looks like extra work and complexity.
The problem is that it is hard to determine file location paths when functional test is run.
The test needs to not reference the build directory structure. And installing objects somewhere in build
requires more steps in meson. This was the easiest way which is what the cfgfiles test was already doing.
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v2 0/5] BPF enhancements
2025-10-30 17:34 [PATCH 0/5] bpf enhancements Stephen Hemminger
` (5 preceding siblings ...)
2025-10-31 11:39 ` [PATCH 0/5] bpf enhancements Marat Khalili
@ 2025-10-31 16:41 ` Stephen Hemminger
2025-10-31 16:41 ` [PATCH v2 1/5] bpf: add allocation annotations to functions Stephen Hemminger
` (4 more replies)
6 siblings, 5 replies; 14+ messages in thread
From: Stephen Hemminger @ 2025-10-31 16:41 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger
This is a series of fixes and tests for BPF functionality.
The new tests for Tx and Rx filtering require the infrastructure
to build a bpf program (clang) and the tool to convert object
file to header (xxd). If missing the test will get skipped.
v2 - add more checks in rx test
fix headers
Stephen Hemminger (5):
bpf: add allocation annotations to functions
bpf: use bulk free on filtered packets
bpf: add a test for BPF ELF load
bpf: add test for Rx and Tx filtering
bpf: remove use of VLA
app/test/bpf/meson.build | 53 ++++
app/test/bpf/test_bpf_filter.c | 53 ++++
app/test/bpf/test_bpf_load.c | 62 +++++
app/test/meson.build | 2 +
app/test/test_bpf.c | 452 +++++++++++++++++++++++++++++++++
lib/bpf/bpf_pkt.c | 84 +++---
lib/bpf/meson.build | 2 -
lib/bpf/rte_bpf.h | 11 +-
8 files changed, 678 insertions(+), 41 deletions(-)
create mode 100644 app/test/bpf/meson.build
create mode 100644 app/test/bpf/test_bpf_filter.c
create mode 100644 app/test/bpf/test_bpf_load.c
--
2.51.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v2 1/5] bpf: add allocation annotations to functions
2025-10-31 16:41 ` [PATCH v2 0/5] BPF enhancements Stephen Hemminger
@ 2025-10-31 16:41 ` Stephen Hemminger
2025-10-31 16:41 ` [PATCH v2 2/5] bpf: use bulk free on filtered packets Stephen Hemminger
` (3 subsequent siblings)
4 siblings, 0 replies; 14+ messages in thread
From: Stephen Hemminger @ 2025-10-31 16:41 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Konstantin Ananyev
In commit 80da7efbb4c4 ("eal: annotate allocation functions")
helper macros were added to provide compiler information to
detect misuse of alloc/free combinations. This covered many
of the functions in DPDK but missed the case of data allocated
by BPF load functions.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
lib/bpf/rte_bpf.h | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/lib/bpf/rte_bpf.h b/lib/bpf/rte_bpf.h
index 80ebb0210f..309d84bc51 100644
--- a/lib/bpf/rte_bpf.h
+++ b/lib/bpf/rte_bpf.h
@@ -18,6 +18,7 @@
#include <rte_common.h>
#include <rte_mbuf.h>
+#include <rte_malloc.h>
#include <bpf_def.h>
#ifdef __cplusplus
@@ -128,7 +129,8 @@ rte_bpf_destroy(struct rte_bpf *bpf);
* - ENOMEM - can't reserve enough memory
*/
struct rte_bpf *
-rte_bpf_load(const struct rte_bpf_prm *prm);
+rte_bpf_load(const struct rte_bpf_prm *prm)
+ __rte_malloc __rte_dealloc(rte_bpf_destroy, 1);
/**
* Create a new eBPF execution context and load BPF code from given ELF
@@ -152,7 +154,9 @@ rte_bpf_load(const struct rte_bpf_prm *prm);
*/
struct rte_bpf *
rte_bpf_elf_load(const struct rte_bpf_prm *prm, const char *fname,
- const char *sname);
+ const char *sname)
+ __rte_malloc __rte_dealloc(rte_bpf_destroy, 1);
+
/**
* Execute given BPF bytecode.
*
@@ -228,7 +232,8 @@ struct bpf_program;
* - ENOTSUP - operation not supported
*/
struct rte_bpf_prm *
-rte_bpf_convert(const struct bpf_program *prog);
+rte_bpf_convert(const struct bpf_program *prog)
+ __rte_malloc __rte_dealloc_free;
#ifdef __cplusplus
}
--
2.51.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v2 2/5] bpf: use bulk free on filtered packets
2025-10-31 16:41 ` [PATCH v2 0/5] BPF enhancements Stephen Hemminger
2025-10-31 16:41 ` [PATCH v2 1/5] bpf: add allocation annotations to functions Stephen Hemminger
@ 2025-10-31 16:41 ` Stephen Hemminger
2025-10-31 16:41 ` [PATCH v2 3/5] bpf: add a test for BPF ELF load Stephen Hemminger
` (2 subsequent siblings)
4 siblings, 0 replies; 14+ messages in thread
From: Stephen Hemminger @ 2025-10-31 16:41 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Konstantin Ananyev
When rte_pktmbuf_free_bulk is faster because it does single
mempool operation rather than per-packet.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
lib/bpf/bpf_pkt.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/lib/bpf/bpf_pkt.c b/lib/bpf/bpf_pkt.c
index 01f813c56b..087ac0f244 100644
--- a/lib/bpf/bpf_pkt.c
+++ b/lib/bpf/bpf_pkt.c
@@ -177,8 +177,7 @@ apply_filter(struct rte_mbuf *mb[], const uint64_t rc[], uint32_t num,
if (drop != 0) {
/* free filtered out mbufs */
- for (i = 0; i != k; i++)
- rte_pktmbuf_free(dr[i]);
+ rte_pktmbuf_free_bulk(dr, k);
} else {
/* copy filtered out mbufs beyond good ones */
for (i = 0; i != k; i++)
--
2.51.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v2 3/5] bpf: add a test for BPF ELF load
2025-10-31 16:41 ` [PATCH v2 0/5] BPF enhancements Stephen Hemminger
2025-10-31 16:41 ` [PATCH v2 1/5] bpf: add allocation annotations to functions Stephen Hemminger
2025-10-31 16:41 ` [PATCH v2 2/5] bpf: use bulk free on filtered packets Stephen Hemminger
@ 2025-10-31 16:41 ` Stephen Hemminger
2025-10-31 16:41 ` [PATCH v2 4/5] bpf: add test for Rx and Tx filtering Stephen Hemminger
2025-10-31 16:41 ` [PATCH v2 5/5] bpf: remove use of VLA Stephen Hemminger
4 siblings, 0 replies; 14+ messages in thread
From: Stephen Hemminger @ 2025-10-31 16:41 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Konstantin Ananyev
Create an ELF file to load using clang.
Repackage the object into an array using xdd.
Write a test to see load and run the BPF.
Draft version made with Claude AI, but it didn't work.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
app/test/bpf/meson.build | 52 +++++++++++++
app/test/bpf/test_bpf_load.c | 62 +++++++++++++++
app/test/meson.build | 2 +
app/test/test_bpf.c | 146 +++++++++++++++++++++++++++++++++++
4 files changed, 262 insertions(+)
create mode 100644 app/test/bpf/meson.build
create mode 100644 app/test/bpf/test_bpf_load.c
diff --git a/app/test/bpf/meson.build b/app/test/bpf/meson.build
new file mode 100644
index 0000000000..2b944f5ea9
--- /dev/null
+++ b/app/test/bpf/meson.build
@@ -0,0 +1,52 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright 2025 Stephen Hemminger <stephen@networkplumber.org>
+
+bpf_test_hdrs = [ ]
+
+# use clang to compile to bpf
+clang_supports_bpf = false
+clang = find_program('clang', required: false)
+if clang.found()
+ clang_supports_bpf = run_command(clang, '-target', 'bpf', '--print-supported-cpus',
+ check: false).returncode() == 0
+endif
+
+if not clang_supports_bpf
+ message('app/test_bpf: no BPF load tests missing clang BPF support')
+ subdir_done()
+
+endif
+
+xxd = find_program('xxd', required: false)
+if not xxd.found()
+ message('app/test_bpf: missing xxd required to convert object to array')
+ subdir_done()
+endif
+
+# BPF compiler flags
+bpf_cflags = [ '-O2', '-target', 'bpf', '-g', '-c']
+
+# Enable test in test_bpf.c
+cflags += '-DTEST_BPF_ELF_LOAD'
+
+# BPF sources to compile
+test_bpf_progs = [
+ 'test_bpf_load'
+]
+
+foreach test_name : test_bpf_progs
+ # Compile BPF C source to object file
+ bpf_obj = custom_target(test_name + '_o',
+ input: test_name + '.c',
+ output: test_name + '.o',
+ command: [ clang, bpf_cflags, '@INPUT@', '-o', '@OUTPUT@'])
+
+ # Convert object file to C header using xxd
+ bpf_test_h = custom_target(test_name + '_h',
+ input: bpf_obj,
+ output: test_name + '.h',
+ command: [ xxd, '-i', '-n', test_name + '_data','@INPUT@', '@OUTPUT@'])
+
+ resources += bpf_test_h
+
+endforeach
diff --git a/app/test/bpf/test_bpf_load.c b/app/test/bpf/test_bpf_load.c
new file mode 100644
index 0000000000..9678c110d9
--- /dev/null
+++ b/app/test/bpf/test_bpf_load.c
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * BPF program for testing rte_bpf_elf_load
+ */
+
+typedef unsigned char uint8_t;
+typedef unsigned short uint16_t;
+typedef unsigned int uint32_t;
+typedef unsigned long uint64_t;
+
+/* Match the structures from test_bpf.c */
+struct dummy_offset {
+ uint64_t u64;
+ uint32_t u32;
+ uint16_t u16;
+ uint8_t u8;
+} __attribute__((packed));
+
+struct dummy_vect8 {
+ struct dummy_offset in[8];
+ struct dummy_offset out[8];
+};
+
+/* External function declaration - provided by test via xsym */
+extern void dummy_func1(const void *p, uint32_t *v32, uint64_t *v64);
+
+/*
+ * Test BPF function that will be loaded from ELF
+ * This function:
+ * 1. Reads values from input structure
+ * 2. Performs some computations
+ * 3. Writes results to output structure
+ * 4. Returns sum of values
+ */
+__attribute__((section("func"), used))
+uint64_t
+test_func(struct dummy_vect8 *arg)
+{
+ uint64_t sum = 0;
+ uint32_t v32;
+ uint64_t v64;
+
+ /* Load input values */
+ v32 = arg->in[0].u32;
+ v64 = arg->in[0].u64;
+
+ /* Call external function */
+ dummy_func1(arg, &v32, &v64);
+
+ /* Store results */
+ arg->out[0].u32 = v32;
+ arg->out[0].u64 = v64;
+
+ /* Calculate sum */
+ sum = arg->in[0].u64;
+ sum += arg->in[0].u32;
+ sum += arg->in[0].u16;
+ sum += arg->in[0].u8;
+ sum += v32;
+ sum += v64;
+
+ return sum;
+}
diff --git a/app/test/meson.build b/app/test/meson.build
index 8df8d3edd1..efec42a6bf 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -281,6 +281,8 @@ if not is_windows
install: false)
endif
+subdir('bpf')
+
subdir('test_cfgfiles')
resources += test_cfgfile_h
diff --git a/app/test/test_bpf.c b/app/test/test_bpf.c
index 90e10d7d2c..855fdc8ad1 100644
--- a/app/test/test_bpf.c
+++ b/app/test/test_bpf.c
@@ -6,6 +6,7 @@
#include <string.h>
#include <stdint.h>
#include <inttypes.h>
+#include <unistd.h>
#include <rte_memory.h>
#include <rte_debug.h>
@@ -14,6 +15,8 @@
#include <rte_random.h>
#include <rte_byteorder.h>
#include <rte_errno.h>
+#include <rte_bpf.h>
+
#include "test.h"
#if !defined(RTE_LIB_BPF)
@@ -3278,6 +3281,149 @@ test_bpf(void)
REGISTER_FAST_TEST(bpf_autotest, true, true, test_bpf);
+#ifdef TEST_BPF_ELF_LOAD
+
+/*
+ * Helper function to write BPF object data to temporary file.
+ * Returns temp file path on success, NULL on failure.
+ * Caller must free the returned path and unlink the file.
+ */
+static char *
+create_temp_bpf_file(const uint8_t *data, size_t size, const char *suffix)
+{
+ char *tmpfile = NULL;
+ int fd;
+ ssize_t written;
+
+ if (asprintf(&tmpfile, "/tmp/dpdk_bpf_%s_XXXXXX", suffix) < 0) {
+ printf("%s@%d: asprintf failed: %s\n",
+ __func__, __LINE__, strerror(errno));
+ return NULL;
+ }
+
+ /* Create and open temp file */
+ fd = mkstemp(tmpfile);
+ if (fd < 0) {
+ printf("%s@%d: mkstemp(%s) failed: %s\n",
+ __func__, __LINE__, tmpfile, strerror(errno));
+ free(tmpfile);
+ return NULL;
+ }
+
+ /* Write BPF object data */
+ written = write(fd, data, size);
+ close(fd);
+
+ if (written != (ssize_t)size) {
+ printf("%s@%d: write failed: %s\n",
+ __func__, __LINE__, strerror(errno));
+ unlink(tmpfile);
+ free(tmpfile);
+ return NULL;
+ }
+
+ return tmpfile;
+}
+
+#include "test_bpf_load.h"
+
+static int
+test_bpf_elf_load(void)
+{
+ uint8_t tbuf[sizeof(struct dummy_vect8)];
+ const struct rte_bpf_xsym xsym[] = {
+ {
+ .name = RTE_STR(dummy_func1),
+ .type = RTE_BPF_XTYPE_FUNC,
+ .func = {
+ .val = (void *)dummy_func1,
+ .nb_args = 3,
+ .args = {
+ [0] = {
+ .type = RTE_BPF_ARG_PTR,
+ .size = sizeof(struct dummy_offset),
+ },
+ [1] = {
+ .type = RTE_BPF_ARG_PTR,
+ .size = sizeof(uint32_t),
+ },
+ [2] = {
+ .type = RTE_BPF_ARG_PTR,
+ .size = sizeof(uint64_t),
+ },
+ },
+ },
+ },
+ };
+ int ret;
+
+ /* Create temp file from embedded BPF object */
+ char *tmpfile = create_temp_bpf_file(test_bpf_load_data,
+ test_bpf_load_data_len, "load");
+ if (tmpfile == NULL)
+ return -1;
+
+ /* Try to load BPF program from temp file */
+ const struct rte_bpf_prm prm = {
+ .xsym = xsym,
+ .nb_xsym = RTE_DIM(xsym),
+ .prog_arg = {
+ .type = RTE_BPF_ARG_PTR,
+ .size = sizeof(tbuf),
+ },
+ };
+ struct rte_bpf *bpf = rte_bpf_elf_load(&prm, tmpfile, "func");
+ TEST_ASSERT(bpf != NULL, "failed to load BPF from %s %d:%s",
+ tmpfile, rte_errno, strerror(rte_errno));
+
+ /* Prepare test data */
+ struct dummy_vect8 *dv = (struct dummy_vect8 *)tbuf;
+ memset(dv, 0, sizeof(*dv));
+ dv->in[0].u64 = (int32_t)TEST_FILL_1;
+ dv->in[0].u32 = dv->in[0].u64;
+ dv->in[0].u16 = dv->in[0].u64;
+ dv->in[0].u8 = dv->in[0].u64;
+
+ /* Execute loaded BPF program */
+ uint64_t sum = rte_bpf_exec(bpf, tbuf);
+ TEST_ASSERT(sum != 0, "BPF execution returned: %" PRIu64, sum);
+
+ /* Test JIT if available */
+ struct rte_bpf_jit jit;
+ ret = rte_bpf_get_jit(bpf, &jit);
+ TEST_ASSERT(ret == 0, "rte_bpf_get_jit failed: %d", ret);
+
+ if (jit.func != NULL) {
+ memset(dv, 0, sizeof(*dv));
+ dv->in[0].u64 = (int32_t)TEST_FILL_1;
+ dv->in[0].u32 = dv->in[0].u64;
+ dv->in[0].u16 = dv->in[0].u64;
+ dv->in[0].u8 = dv->in[0].u64;
+
+ uint64_t jsum = jit.func(tbuf);
+ TEST_ASSERT_EQUAL(sum, jsum, "BPF JIT execution difference");
+ }
+
+ rte_bpf_destroy(bpf);
+ unlink(tmpfile);
+ free(tmpfile);
+
+ printf("%s: ELF load test passed\n", __func__);
+ return TEST_SUCCESS;
+}
+#else
+
+static int
+test_bpf_elf_load(void)
+{
+ printf("BPF compile not supported, skipping test\n");
+ return TEST_SKIPPED;
+}
+
+#endif /* !TEST_BPF_ELF_LOAD */
+
+REGISTER_FAST_TEST(bpf_elf_load_autotest, true, true, test_bpf_elf_load);
+
#ifndef RTE_HAS_LIBPCAP
static int
--
2.51.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v2 4/5] bpf: add test for Rx and Tx filtering
2025-10-31 16:41 ` [PATCH v2 0/5] BPF enhancements Stephen Hemminger
` (2 preceding siblings ...)
2025-10-31 16:41 ` [PATCH v2 3/5] bpf: add a test for BPF ELF load Stephen Hemminger
@ 2025-10-31 16:41 ` Stephen Hemminger
2025-10-31 16:41 ` [PATCH v2 5/5] bpf: remove use of VLA Stephen Hemminger
4 siblings, 0 replies; 14+ messages in thread
From: Stephen Hemminger @ 2025-10-31 16:41 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Konstantin Ananyev
New test using null device to test filtering with BPF.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
Note: the BPF source will generate some false positives from checkpatch
because it is not regular DPDK source, and must not use DPDK macros.
app/test/bpf/meson.build | 3 +-
app/test/bpf/test_bpf_filter.c | 53 ++++++
app/test/test_bpf.c | 306 +++++++++++++++++++++++++++++++++
3 files changed, 361 insertions(+), 1 deletion(-)
create mode 100644 app/test/bpf/test_bpf_filter.c
diff --git a/app/test/bpf/meson.build b/app/test/bpf/meson.build
index 2b944f5ea9..be7c643b41 100644
--- a/app/test/bpf/meson.build
+++ b/app/test/bpf/meson.build
@@ -31,7 +31,8 @@ cflags += '-DTEST_BPF_ELF_LOAD'
# BPF sources to compile
test_bpf_progs = [
- 'test_bpf_load'
+ 'test_bpf_load',
+ 'test_bpf_filter',
]
foreach test_name : test_bpf_progs
diff --git a/app/test/bpf/test_bpf_filter.c b/app/test/bpf/test_bpf_filter.c
new file mode 100644
index 0000000000..d47233a47a
--- /dev/null
+++ b/app/test/bpf/test_bpf_filter.c
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * BPF TX filter program for testing rte_bpf_eth_tx_elf_load
+ */
+
+typedef unsigned char uint8_t;
+typedef unsigned short uint16_t;
+typedef unsigned int uint32_t;
+typedef unsigned long uint64_t;
+
+/*
+ * Simple TX filter that accepts TCP packets
+ *
+ * BPF TX programs receive pointer to data and should return:
+ * 0 = drop packet
+ * non-zero = rx/tx packet
+ *
+ * This filter checks:
+ * 1. Packet is IPv4
+ * 2. Protocol is TCP (IPPROTO_TCP = 6)
+ */
+__attribute__((section("filter"), used))
+uint64_t
+test_filter(void *pkt)
+{
+ uint8_t *data = pkt;
+
+ /* Read version and IHL (first byte of IP header) */
+ uint8_t version_ihl = data[14];
+
+ /* Check IPv4 version (upper 4 bits should be 4) */
+ if ((version_ihl >> 4) != 4)
+ return 0;
+
+ /* Protocol field (byte 9 of IP header) must be TCP (6) */
+ uint8_t proto = data[14 + 9];
+ return (proto == 6);
+}
+
+__attribute__((section("drop"), used))
+uint64_t
+test_drop(void *pkt)
+{
+ (void)pkt;
+ return 0;
+}
+
+__attribute__((section("allow"), used))
+uint64_t
+test_allow(void *pkt)
+{
+ (void)pkt;
+ return 1;
+}
diff --git a/app/test/test_bpf.c b/app/test/test_bpf.c
index 855fdc8ad1..2b34f4986d 100644
--- a/app/test/test_bpf.c
+++ b/app/test/test_bpf.c
@@ -16,6 +16,12 @@
#include <rte_byteorder.h>
#include <rte_errno.h>
#include <rte_bpf.h>
+#include <rte_ethdev.h>
+#include <rte_bpf_ethdev.h>
+#include <rte_bus_vdev.h>
+#include <rte_ether.h>
+#include <rte_ip.h>
+#include <rte_tcp.h>
#include "test.h"
@@ -3411,6 +3417,290 @@ test_bpf_elf_load(void)
printf("%s: ELF load test passed\n", __func__);
return TEST_SUCCESS;
}
+
+#include "test_bpf_filter.h"
+
+#define BPF_TEST_BURST 128
+#define BPF_TEST_POOLSIZE 256 /* at least 2x burst */
+#define BPF_TEST_PKT_LEN 64 /* Ether + IP + TCP */
+
+static int null_vdev_setup(const char *name, uint16_t *port, struct rte_mempool *pool)
+{
+ int ret;
+
+ /* Make a null device */
+ 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);
+
+ struct rte_eth_conf conf = { };
+ ret = rte_eth_dev_configure(*port, 1, 1, &conf);
+ TEST_ASSERT(ret == 0, "failed to configure port %u: %d", *port, ret);
+
+ struct rte_eth_txconf txconf = { };
+ 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);
+
+ struct rte_eth_rxconf rxconf = { };
+ 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;
+}
+
+static unsigned int
+setup_mbufs(struct rte_mbuf *burst[], unsigned int n)
+{
+ struct rte_ether_hdr eh = {
+ .ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4),
+ };
+ const struct rte_ipv4_hdr iph = {
+ .version_ihl = RTE_IPV4_VHL_DEF,
+ .total_length = rte_cpu_to_be_16(BPF_TEST_PKT_LEN - sizeof(eh)),
+ .time_to_live = IPDEFTTL,
+ .src_addr = rte_cpu_to_be_32(ip_src_addr),
+ .dst_addr = rte_cpu_to_be_32(ip_dst_addr),
+ };
+ unsigned int tcp_count = 0;
+
+ rte_eth_random_addr(eh.dst_addr.addr_bytes);
+
+ for (unsigned int i = 0; i < n; i++) {
+ struct rte_mbuf *mb = burst[i];
+
+ /* Setup Ethernet header */
+ *rte_pktmbuf_mtod(mb, struct rte_ether_hdr *) = eh;
+
+ /* Setup IP header */
+ struct rte_ipv4_hdr *ip
+ = rte_pktmbuf_mtod_offset(mb, struct rte_ipv4_hdr *, sizeof(eh));
+ *ip = iph;
+
+ if (rte_rand() & 1) {
+ struct rte_udp_hdr *udp
+ = rte_pktmbuf_mtod_offset(mb, struct rte_udp_hdr *,
+ sizeof(eh) + sizeof(iph));
+
+ ip->next_proto_id = IPPROTO_UDP;
+ *udp = (struct rte_udp_hdr) {
+ .src_port = rte_cpu_to_be_16(9), /* discard */
+ .dst_port = rte_cpu_to_be_16(9), /* discard */
+ .dgram_len = BPF_TEST_PKT_LEN - sizeof(eh) - sizeof(iph),
+ };
+
+ } else {
+ struct rte_tcp_hdr *tcp
+ = rte_pktmbuf_mtod_offset(mb, struct rte_tcp_hdr *,
+ sizeof(eh) + sizeof(iph));
+
+ ip->next_proto_id = IPPROTO_TCP;
+ *tcp = (struct rte_tcp_hdr) {
+ .src_port = rte_cpu_to_be_16(9), /* discard */
+ .dst_port = rte_cpu_to_be_16(9), /* discard */
+ .tcp_flags = RTE_TCP_RST_FLAG,
+ };
+ ++tcp_count;
+ }
+ }
+
+ return tcp_count;
+}
+
+static int bpf_tx_test(uint16_t port, const char *tmpfile, struct rte_mempool *pool,
+ const char *fname, uint32_t flags)
+{
+ const struct rte_bpf_prm prm = {
+ .prog_arg = {
+ .type = RTE_BPF_ARG_PTR,
+ .size = sizeof(struct rte_mbuf),
+ },
+ };
+ int ret;
+
+ struct rte_mbuf *pkts[BPF_TEST_BURST] = { };
+ ret = rte_pktmbuf_alloc_bulk(pool, pkts, BPF_TEST_BURST);
+ TEST_ASSERT(ret == 0, "failed to allocate mbufs");
+
+ uint16_t expect = setup_mbufs(pkts, BPF_TEST_BURST);
+
+ /* Try to load BPF TX program from temp file */
+ ret = rte_bpf_eth_tx_elf_load(port, 0, &prm, tmpfile, fname, flags);
+ TEST_ASSERT(ret == 0, "failed to load BPF filter from temp file %s: %d",
+ tmpfile, ret);
+
+ uint16_t sent = rte_eth_tx_burst(port, 0, pkts, BPF_TEST_BURST);
+ TEST_ASSERT_EQUAL(sent, expect, "rte_eth_tx_burst returned: %u expected %u",
+ sent, expect);
+
+ /* The unsent packets should be dropped */
+ rte_pktmbuf_free_bulk(pkts + sent, BPF_TEST_BURST - sent);
+
+ /* Pool should have same number of packets avail */
+ unsigned int avail = rte_mempool_avail_count(pool);
+ TEST_ASSERT_EQUAL(avail, BPF_TEST_POOLSIZE,
+ "Mempool available %u != %u leaks?", avail, BPF_TEST_POOLSIZE);
+
+ rte_bpf_eth_tx_unload(port, 0);
+ return TEST_SUCCESS;
+}
+
+static int
+test_bpf_elf_tx_load(void)
+{
+ static const char null_dev[] = "net_null_bpf0";
+ char *tmpfile = NULL;
+ struct rte_mempool *mb_pool = NULL;
+ uint16_t port = UINT16_MAX;
+ int ret;
+
+ printf("%s start\n", __func__);
+
+ /* Make a pool for packets */
+ mb_pool = rte_pktmbuf_pool_create("bpf_tx_test_pool", BPF_TEST_POOLSIZE,
+ 0, 0, RTE_MBUF_DEFAULT_BUF_SIZE,
+ SOCKET_ID_ANY);
+
+ ret = null_vdev_setup(null_dev, &port, mb_pool);
+ if (ret != 0)
+ goto fail;
+
+ /* Create temp file from embedded BPF object */
+ tmpfile = create_temp_bpf_file(test_bpf_filter_data,
+ test_bpf_filter_data_len, "tx");
+ if (tmpfile == NULL)
+ goto fail;
+
+ /* Do test with VM */
+ ret = bpf_tx_test(port, tmpfile, mb_pool, "filter", 0);
+ if (ret != 0)
+ goto fail;
+
+ /* Repeat with JIT */
+ ret = bpf_tx_test(port, tmpfile, mb_pool, "filter", RTE_BPF_ETH_F_JIT);
+ if (ret == 0)
+ printf("%s: TX ELF load test passed\n", __func__);
+
+fail:
+ if (tmpfile) {
+ unlink(tmpfile);
+ free(tmpfile);
+ }
+
+ if (port != UINT16_MAX)
+ rte_vdev_uninit(null_dev);
+
+ rte_mempool_free(mb_pool);
+
+ return ret == 0 ? TEST_SUCCESS : TEST_FAILED;
+}
+
+static int bpf_rx_test(uint16_t port, const char *tmpfile, struct rte_mempool *pool,
+ const char *fname, uint32_t flags, uint16_t expected)
+{
+ struct rte_mbuf *pkts[BPF_TEST_BURST];
+ const struct rte_bpf_prm prm = {
+ .prog_arg = {
+ .type = RTE_BPF_ARG_PTR,
+ .size = sizeof(struct rte_mbuf),
+ },
+ };
+ int ret;
+
+ /* Load BPF program to drop all packets */
+ ret = rte_bpf_eth_rx_elf_load(port, 0, &prm, tmpfile, fname, flags);
+ TEST_ASSERT(ret == 0, "failed to load BPF filter from temp file %s: %d",
+ tmpfile, ret);
+
+ uint16_t rcvd = rte_eth_rx_burst(port, 0, pkts, BPF_TEST_BURST);
+ TEST_ASSERT_EQUAL(rcvd, expected,
+ "rte_eth_rx_burst returned: %u expect: %u", rcvd, expected);
+
+ /* Drop the received packets */
+ rte_pktmbuf_free_bulk(pkts, rcvd);
+
+ rte_bpf_eth_rx_unload(port, 0);
+
+ /* Pool should now be full */
+ unsigned int avail = rte_mempool_avail_count(pool);
+ TEST_ASSERT_EQUAL(avail, BPF_TEST_POOLSIZE,
+ "Mempool available %u != %u leaks?", avail, BPF_TEST_POOLSIZE);
+
+ return TEST_SUCCESS;
+}
+
+static int
+test_bpf_elf_rx_load(void)
+{
+ static const char null_dev[] = "net_null_bpf0";
+ struct rte_mempool *pool = NULL;
+ char *tmpfile = NULL;
+ uint16_t port;
+ int ret;
+
+ printf("%s start\n", __func__);
+
+ /* Make a pool for packets */
+ pool = rte_pktmbuf_pool_create("bpf_rx_test_pool", 2 * BPF_TEST_BURST,
+ 0, 0, RTE_MBUF_DEFAULT_BUF_SIZE,
+ SOCKET_ID_ANY);
+ TEST_ASSERT(pool != NULL, "failed to create mempool");
+
+ ret = null_vdev_setup(null_dev, &port, pool);
+ if (ret != 0)
+ goto fail;
+
+ /* Create temp file from embedded BPF object */
+ tmpfile = create_temp_bpf_file(test_bpf_filter_data,
+ test_bpf_filter_data_len, "tx");
+ if (tmpfile == NULL)
+ goto fail;
+
+ /* Do test with VM */
+ ret = bpf_rx_test(port, tmpfile, pool, "drop", 0, 0);
+ if (ret != 0)
+ goto fail;
+
+ /* Repeat with JIT */
+ ret = bpf_rx_test(port, tmpfile, pool, "drop", RTE_BPF_ETH_F_JIT, 0);
+ if (ret != 0)
+ goto fail;
+
+ /* Repeat with allow all */
+ ret = bpf_rx_test(port, tmpfile, pool, "allow", 0, BPF_TEST_BURST);
+ if (ret != 0)
+ goto fail;
+
+ /* Repeat with JIT */
+ ret = bpf_rx_test(port, tmpfile, pool, "allow", RTE_BPF_ETH_F_JIT, BPF_TEST_BURST);
+ if (ret != 0)
+ goto fail;
+
+ printf("%s: RX ELF load test passed\n", __func__);
+
+ /* The filter should free the mbufs */
+ unsigned int avail = rte_mempool_avail_count(pool);
+ TEST_ASSERT_EQUAL(avail, BPF_TEST_POOLSIZE,
+ "Mempool available %u != %u leaks?", avail, BPF_TEST_POOLSIZE);
+
+fail:
+ if (tmpfile) {
+ unlink(tmpfile);
+ free(tmpfile);
+ }
+
+ if (port != UINT16_MAX)
+ rte_vdev_uninit(null_dev);
+
+ rte_mempool_free(pool);
+
+ return ret == 0 ? TEST_SUCCESS : TEST_FAILED;
+}
#else
static int
@@ -3420,9 +3710,25 @@ test_bpf_elf_load(void)
return TEST_SKIPPED;
}
+static int
+test_bpf_elf_tx_load(void)
+{
+ printf("BPF compile not supported, skipping Tx test\n");
+ return TEST_SKIPPED;
+}
+
+static int
+test_bpf_elf_rx_load(void)
+{
+ printf("BPF compile not supported, skipping Tx test\n");
+ return TEST_SKIPPED;
+}
+
#endif /* !TEST_BPF_ELF_LOAD */
REGISTER_FAST_TEST(bpf_elf_load_autotest, true, true, test_bpf_elf_load);
+REGISTER_FAST_TEST(bpf_eth_tx_elf_load_autotest, true, true, test_bpf_elf_tx_load);
+REGISTER_FAST_TEST(bpf_eth_rx_elf_load_autotest, true, true, test_bpf_elf_rx_load);
#ifndef RTE_HAS_LIBPCAP
--
2.51.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v2 5/5] bpf: remove use of VLA
2025-10-31 16:41 ` [PATCH v2 0/5] BPF enhancements Stephen Hemminger
` (3 preceding siblings ...)
2025-10-31 16:41 ` [PATCH v2 4/5] bpf: add test for Rx and Tx filtering Stephen Hemminger
@ 2025-10-31 16:41 ` Stephen Hemminger
4 siblings, 0 replies; 14+ messages in thread
From: Stephen Hemminger @ 2025-10-31 16:41 UTC (permalink / raw)
To: dev; +Cc: Stephen Hemminger, Konstantin Ananyev
Replace variable length array with two part loop which
applies filter in bursts up to 32 packets.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
lib/bpf/bpf_pkt.c | 81 ++++++++++++++++++++++++++-------------------
lib/bpf/meson.build | 2 --
2 files changed, 47 insertions(+), 36 deletions(-)
diff --git a/lib/bpf/bpf_pkt.c b/lib/bpf/bpf_pkt.c
index 087ac0f244..15d36739a0 100644
--- a/lib/bpf/bpf_pkt.c
+++ b/lib/bpf/bpf_pkt.c
@@ -157,13 +157,16 @@ bpf_eth_cbh_add(struct bpf_eth_cbh *cbh, uint16_t port, uint16_t queue)
/*
* BPF packet processing routines.
*/
+#define BPF_FILTER_BURST 32u
static inline uint32_t
apply_filter(struct rte_mbuf *mb[], const uint64_t rc[], uint32_t num,
uint32_t drop)
{
uint32_t i, j, k;
- struct rte_mbuf *dr[num];
+ struct rte_mbuf *dr[BPF_FILTER_BURST];
+
+ RTE_ASSERT(num <= BPF_FILTER_BURST);
for (i = 0, j = 0, k = 0; i != num; i++) {
@@ -191,65 +194,75 @@ static inline uint32_t
pkt_filter_vm(const struct rte_bpf *bpf, struct rte_mbuf *mb[], uint32_t num,
uint32_t drop)
{
- uint32_t i;
- void *dp[num];
- uint64_t rc[num];
+ uint32_t matched = 0;
+
+ for (uint32_t i = 0; i < num; i += BPF_FILTER_BURST) {
+ uint32_t burst_sz = RTE_MIN(num, BPF_FILTER_BURST);
+ void *dp[BPF_FILTER_BURST];
+ uint64_t rc[BPF_FILTER_BURST];
- for (i = 0; i != num; i++)
- dp[i] = rte_pktmbuf_mtod(mb[i], void *);
+ for (uint32_t j = 0; j < burst_sz; j++)
+ dp[j] = rte_pktmbuf_mtod(mb[i + j], void *);
- rte_bpf_exec_burst(bpf, dp, rc, num);
- return apply_filter(mb, rc, num, drop);
+ rte_bpf_exec_burst(bpf, dp, rc, burst_sz);
+ matched += apply_filter(mb + i, rc, burst_sz, drop);
+ }
+ return matched;
}
static inline uint32_t
pkt_filter_jit(const struct rte_bpf_jit *jit, struct rte_mbuf *mb[],
uint32_t num, uint32_t drop)
{
- uint32_t i, n;
- void *dp;
- uint64_t rc[num];
-
- n = 0;
- for (i = 0; i != num; i++) {
- dp = rte_pktmbuf_mtod(mb[i], void *);
- rc[i] = jit->func(dp);
- n += (rc[i] == 0);
- }
+ uint32_t matched = 0;
+
+ for (uint32_t i = 0; i < num; i += BPF_FILTER_BURST) {
+ uint32_t burst_sz = RTE_MIN(num, BPF_FILTER_BURST);
+ uint64_t rc[BPF_FILTER_BURST];
+
+ for (uint32_t j = 0; j < burst_sz; j++) {
+ void *dp = rte_pktmbuf_mtod(mb[i + j], void *);
- if (n != 0)
- num = apply_filter(mb, rc, num, drop);
+ rc[j] = jit->func(dp);
+ }
- return num;
+ matched += apply_filter(mb + i, rc, burst_sz, drop);
+ }
+ return matched;
}
static inline uint32_t
pkt_filter_mb_vm(const struct rte_bpf *bpf, struct rte_mbuf *mb[], uint32_t num,
uint32_t drop)
{
- uint64_t rc[num];
+ uint32_t matched = 0;
+
+ for (uint32_t i = 0; i < num; i += BPF_FILTER_BURST) {
+ uint32_t burst_sz = RTE_MIN(num, BPF_FILTER_BURST);
+ uint64_t rc[BPF_FILTER_BURST];
- rte_bpf_exec_burst(bpf, (void **)mb, rc, num);
- return apply_filter(mb, rc, num, drop);
+ rte_bpf_exec_burst(bpf, (void **)mb, rc, burst_sz);
+ matched += apply_filter(mb + i, rc, burst_sz, drop);
+ }
+ return matched;
}
static inline uint32_t
pkt_filter_mb_jit(const struct rte_bpf_jit *jit, struct rte_mbuf *mb[],
uint32_t num, uint32_t drop)
{
- uint32_t i, n;
- uint64_t rc[num];
+ uint32_t matched = 0;
- n = 0;
- for (i = 0; i != num; i++) {
- rc[i] = jit->func(mb[i]);
- n += (rc[i] == 0);
- }
+ for (uint32_t i = 0; i < num; i += BPF_FILTER_BURST) {
+ uint32_t burst_sz = RTE_MIN(num, BPF_FILTER_BURST);
+ uint64_t rc[BPF_FILTER_BURST];
- if (n != 0)
- num = apply_filter(mb, rc, num, drop);
+ for (uint32_t j = 0; j < burst_sz; j++)
+ rc[j] = jit->func(mb[i + j]);
- return num;
+ matched += apply_filter(mb + i, rc, burst_sz, drop);
+ }
+ return matched;
}
/*
diff --git a/lib/bpf/meson.build b/lib/bpf/meson.build
index 28df7f469a..aa258a9061 100644
--- a/lib/bpf/meson.build
+++ b/lib/bpf/meson.build
@@ -7,8 +7,6 @@ if is_windows
subdir_done()
endif
-cflags += no_wvla_cflag
-
if arch_subdir == 'x86' and dpdk_conf.get('RTE_ARCH_32')
build = false
reason = 'not supported on 32-bit x86'
--
2.51.0
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2025-10-31 16:44 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-10-30 17:34 [PATCH 0/5] bpf enhancements Stephen Hemminger
2025-10-30 17:34 ` [PATCH 1/5] bpf: add allocation annotations to functions Stephen Hemminger
2025-10-30 17:34 ` [PATCH 2/5] bpf: use rte_pktmbuf_free_bulk Stephen Hemminger
2025-10-30 17:34 ` [PATCH 3/5] bpf: add a test for BPF ELF load Stephen Hemminger
2025-10-30 17:34 ` [PATCH 4/5] bpf: add test for rx and tx filtering Stephen Hemminger
2025-10-30 17:34 ` [PATCH 5/5] bpf: remove use of vla Stephen Hemminger
2025-10-31 11:39 ` [PATCH 0/5] bpf enhancements Marat Khalili
2025-10-31 16:37 ` Stephen Hemminger
2025-10-31 16:41 ` [PATCH v2 0/5] BPF enhancements Stephen Hemminger
2025-10-31 16:41 ` [PATCH v2 1/5] bpf: add allocation annotations to functions Stephen Hemminger
2025-10-31 16:41 ` [PATCH v2 2/5] bpf: use bulk free on filtered packets Stephen Hemminger
2025-10-31 16:41 ` [PATCH v2 3/5] bpf: add a test for BPF ELF load Stephen Hemminger
2025-10-31 16:41 ` [PATCH v2 4/5] bpf: add test for Rx and Tx filtering Stephen Hemminger
2025-10-31 16:41 ` [PATCH v2 5/5] bpf: remove use of VLA Stephen Hemminger
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).