DPDK patches and discussions
 help / color / mirror / Atom feed
* [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
                   ` (4 more replies)
  0 siblings, 5 replies; 6+ 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] 6+ 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
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 6+ 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] 6+ 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
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 6+ 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] 6+ 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
  2025-10-30 17:34 ` [PATCH 5/5] bpf: remove use of vla Stephen Hemminger
  4 siblings, 0 replies; 6+ 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] 6+ 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
  4 siblings, 0 replies; 6+ 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] 6+ 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
  4 siblings, 0 replies; 6+ 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] 6+ messages in thread

end of thread, other threads:[~2025-10-30 17:38 UTC | newest]

Thread overview: 6+ 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

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).