Skip to contentSkip to conversation

bpf enhancements

PATCH Superseded Stephen Hemminger 4 participants 61 messages Oct 30, 2025 – Nov 19, 2025 Arm/Amlogic Meson SoC support

lore
Share & tools
lore
#0 lore raw

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

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
Signature · 1 line
2.51.0
#1 lore raw

PATCH 1/5 bpf: add allocation annotations to functions

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.

---
 lib/bpf/rte_bpf.h | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)
lib/bpf/rte_bpf.h +8 -3
index 80ebb0210f..309d84bc51 100644
@@ -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
 }
Signature · 1 line
2.51.0
#2 lore raw

PATCH 2/5 bpf: use rte_pktmbuf_free_bulk

In reply to Stephen Hemminger

When rte_pktmbuf_free_bulk is faster because it does single mempool operation rather than per-packet.

---
 lib/bpf/bpf_pkt.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)
lib/bpf/bpf_pkt.c +1 -2
index 01f813c56b..087ac0f244 100644
@@ -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++)
Signature · 1 line
2.51.0
#3 lore raw

PATCH 3/5 bpf: add a test for BPF ELF load

In reply to Stephen Hemminger

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.

---
 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
app/test/bpf/meson.build new file +52 -0
new file mode 100644
index 0000000000..2b944f5ea9
@@ -0,0 +1,52 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright 2025 Stephen Hemminger <[email protected]>
+
+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
app/test/bpf/test_bpf_load.c new file +60 -0

60 lines not on this page · raw message

app/test/meson.build +2 -0
index 8df8d3edd1..efec42a6bf 100644
@@ -281,6 +281,8 @@ if not is_windows
             install: false)
 endif
 
+subdir('bpf')
+
 subdir('test_cfgfiles')
 
 resources += test_cfgfile_h
app/test/test_bpf.c +146 -0

164 lines not on this page · raw message

Signature · 1 line
2.51.0
#4 lore raw

PATCH 4/5 bpf: add test for rx and tx filtering

In reply to Stephen Hemminger

New test using null device to test filtering with BPF. Initial not working version build with Claude AI.

---
 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
app/test/bpf/meson.build +2 -1
index 2b944f5ea9..be7c643b41 100644
@@ -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
app/test/bpf/test_bpf_filter.c new file +35 -0
new file mode 100644
index 0000000000..f2945b7c73
@@ -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);
+}
app/test/test_bpf.c +280 -0

301 lines not on this page · raw message

Signature · 1 line
2.51.0
#5 lore raw

PATCH 5/5 bpf: remove use of vla

In reply to Stephen Hemminger

Replace variable length array with two part loop which applies filter in bursts up to 32 packets.

---
 lib/bpf/bpf_pkt.c   | 81 ++++++++++++++++++++++++++-------------------
 lib/bpf/meson.build |  2 --
 2 files changed, 47 insertions(+), 36 deletions(-)
lib/bpf/bpf_pkt.c +47 -34
index 087ac0f244..15d36739a0 100644
@@ -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;
 }
 
 /*
lib/bpf/meson.build +0 -2
index 28df7f469a..aa258a9061 100644
@@ -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'
Signature · 1 line
2.51.0
#6 lore raw
In reply to Stephen Hemminger
Quoted 11 lines

-----Original Message-----
From: Stephen Hemminger <[email protected]>
Sent: Thursday 30 October 2025 17:34
To: [email protected]
Cc: Stephen Hemminger <[email protected]>
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.

#7 lore raw
Quoted 7 lines · On Fri, 31 Oct 2025 11:39:54 +0000 Marat Khalili <[email protected]>…
Quoted 7 lines

-----Original Message-----
From: Stephen Hemminger <[email protected]>
Sent: Thursday 30 October 2025 17:34
To: [email protected]
Cc: Stephen Hemminger <[email protected]>
Subject: [PATCH 0/5] bpf enhancements

Quoted 8 lines
Quoted 4 lines

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.

#8 lore raw

v2 cover BPF enhancements

In reply to 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
Signature · 1 line
2.51.0
#9 lore raw

PATCH v2 1/5 bpf: add allocation annotations to functions

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.

---
 lib/bpf/rte_bpf.h | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)
lib/bpf/rte_bpf.h +8 -3
index 80ebb0210f..309d84bc51 100644
@@ -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
 }
Signature · 1 line
2.51.0
#10 lore raw

PATCH v2 2/5 bpf: use bulk free on filtered packets

In reply to Stephen Hemminger

When rte_pktmbuf_free_bulk is faster because it does single mempool operation rather than per-packet.

---
 lib/bpf/bpf_pkt.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)
lib/bpf/bpf_pkt.c +1 -2
index 01f813c56b..087ac0f244 100644
@@ -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++)
Signature · 1 line
2.51.0
#11 lore raw

PATCH v2 3/5 bpf: add a test for BPF ELF load

In reply to Stephen Hemminger

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.

---
 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
app/test/bpf/meson.build new file +52 -0
new file mode 100644
index 0000000000..2b944f5ea9
@@ -0,0 +1,52 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright 2025 Stephen Hemminger <[email protected]>
+
+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
app/test/bpf/test_bpf_load.c new file +62 -0

62 lines not on this page · raw message

app/test/meson.build +2 -0
index 8df8d3edd1..efec42a6bf 100644
@@ -281,6 +281,8 @@ if not is_windows
             install: false)
 endif
 
+subdir('bpf')
+
 subdir('test_cfgfiles')
 
 resources += test_cfgfile_h
app/test/test_bpf.c +146 -0

164 lines not on this page · raw message

Signature · 1 line
2.51.0
#12 lore raw

PATCH v2 4/5 bpf: add test for Rx and Tx filtering

In reply to Stephen Hemminger

New test using null device to test filtering with BPF.

---
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
app/test/bpf/meson.build +2 -1
index 2b944f5ea9..be7c643b41 100644
@@ -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
app/test/bpf/test_bpf_filter.c new file +53 -0

53 lines not on this page · raw message

app/test/test_bpf.c +306 -0

327 lines not on this page · raw message

Signature · 1 line
2.51.0
#13 lore raw

PATCH v2 5/5 bpf: remove use of VLA

In reply to Stephen Hemminger

Replace variable length array with two part loop which applies filter in bursts up to 32 packets.

---
 lib/bpf/bpf_pkt.c   | 81 ++++++++++++++++++++++++++-------------------
 lib/bpf/meson.build |  2 --
 2 files changed, 47 insertions(+), 36 deletions(-)
lib/bpf/bpf_pkt.c +47 -34
index 087ac0f244..15d36739a0 100644
@@ -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;
 }
 
 /*
lib/bpf/meson.build +0 -2
index 28df7f469a..aa258a9061 100644
@@ -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'
Signature · 1 line
2.51.0
#14 lore raw

v3 cover BPF enhancements

In reply to 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.

It is disappointing that there were no examples or tests when the code was submitted.

v3 - do not depend on xxd -n flag which is not present on versions

     before Oct 2024

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/filter.c    |  53 +++++
 app/test/bpf/load.c      |  62 ++++++
 app/test/bpf/meson.build |  53 +++++
 app/test/meson.build     |   2 +
 app/test/test_bpf.c      | 451 +++++++++++++++++++++++++++++++++++++++
 lib/bpf/bpf_pkt.c        |  84 ++++----
 lib/bpf/meson.build      |   2 -
 lib/bpf/rte_bpf.h        |  11 +-
 8 files changed, 677 insertions(+), 41 deletions(-)
 create mode 100644 app/test/bpf/filter.c
 create mode 100644 app/test/bpf/load.c
 create mode 100644 app/test/bpf/meson.build
Signature · 1 line
2.51.0
#15 lore raw

PATCH v3 1/5 bpf: add allocation annotations to functions

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.

---
 lib/bpf/rte_bpf.h | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)
lib/bpf/rte_bpf.h +8 -3
index 80ebb0210f..309d84bc51 100644
@@ -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
 }
Signature · 1 line
2.51.0
Acked-by on v3 1/5
#16 lore raw

RE: [PATCH v3 1/5] bpf: add allocation annotations to functions

Quoted 46 lines

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.

---
 lib/bpf/rte_bpf.h | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)
lib/bpf/rte_bpf.h +6 -2
index 80ebb0210f..309d84bc51 100644
@@ -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.
  *

lib/bpf/rte_bpf.h struct bpf_program;

Quoted 10 lines
lib/bpf/rte_bpf.h +2 -2
@@ -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
 }
--
Quoted 1 line

2.51.0

#17 lore raw

PATCH v3 2/5 bpf: use bulk free on filtered packets

In reply to Stephen Hemminger

When rte_pktmbuf_free_bulk is faster because it does single mempool operation rather than per-packet.

---
 lib/bpf/bpf_pkt.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)
lib/bpf/bpf_pkt.c +1 -2
index 01f813c56b..087ac0f244 100644
@@ -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++)
Signature · 1 line
2.51.0
#18 lore raw

PATCH v3 3/5 bpf: add a test for BPF ELF load

In reply to Stephen Hemminger

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.

---
 app/test/bpf/load.c      |  62 +++++++++++++++++
 app/test/bpf/meson.build |  52 ++++++++++++++
 app/test/meson.build     |   2 +
 app/test/test_bpf.c      | 147 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 263 insertions(+)
 create mode 100644 app/test/bpf/load.c
 create mode 100644 app/test/bpf/meson.build
app/test/bpf/load.c new file +62 -0
new file mode 100644
index 0000000000..9678c110d9
@@ -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;
+}
app/test/bpf/meson.build new file +52 -0

52 lines not on this page · raw message

app/test/meson.build +2 -0
index 8df8d3edd1..efec42a6bf 100644
@@ -281,6 +281,8 @@ if not is_windows
             install: false)
 endif
 
+subdir('bpf')
+
 subdir('test_cfgfiles')
 
 resources += test_cfgfile_h
app/test/test_bpf.c +147 -0

165 lines not on this page · raw message

Signature · 1 line
2.51.0
#19 lore raw

PATCH v3 4/5 bpf: add test for Rx and Tx filtering

In reply to Stephen Hemminger

New test using null device to test filtering with BPF.

---
 app/test/bpf/filter.c    |  53 +++++++
 app/test/bpf/meson.build |   1 +
 app/test/test_bpf.c      | 304 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 358 insertions(+)
 create mode 100644 app/test/bpf/filter.c
app/test/bpf/filter.c new file +53 -0
new file mode 100644
index 0000000000..d47233a47a
@@ -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;
+}
app/test/bpf/meson.build +1 -0
index b4f54aa976..19fec05521 100644
@@ -32,6 +32,7 @@ cflags += '-DTEST_BPF_ELF_LOAD'
 # BPF sources to compile
 bpf_progs = {
     'load' : 'test_bpf_load',
+    'filter' : 'test_bpf_filter',
 }
 
 foreach bpf_src, bpf_hdr : bpf_progs
app/test/test_bpf.c +304 -0

325 lines not on this page · raw message

Signature · 1 line
2.51.0
#20 lore raw

PATCH v3 5/5 bpf: remove use of VLA

In reply to Stephen Hemminger

Replace variable length array with two part loop which applies filter in bursts up to 32 packets.

---
 lib/bpf/bpf_pkt.c   | 81 ++++++++++++++++++++++++++-------------------
 lib/bpf/meson.build |  2 --
 2 files changed, 47 insertions(+), 36 deletions(-)
lib/bpf/bpf_pkt.c +47 -34
index 087ac0f244..15d36739a0 100644
@@ -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;
 }
 
 /*
lib/bpf/meson.build +0 -2
index 28df7f469a..aa258a9061 100644
@@ -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'
Signature · 1 line
2.51.0
#21 lore raw

RE: [PATCH v3 5/5] bpf: remove use of VLA

Quoted 23 lines

Replace variable length array with two part loop which applies filter in bursts up to 32 packets.

---
 lib/bpf/bpf_pkt.c   | 81 ++++++++++++++++++++++++++-------------------
 lib/bpf/meson.build |  2 --
 2 files changed, 47 insertions(+), 36 deletions(-)
lib/bpf/bpf_pkt.c +1 -0
index 087ac0f244..15d36739a0 100644
@@ -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

lib/bpf/bpf_pkt.c bpf_eth_cbh_add(struct bpf_eth_cbh *cbh, uint16_t · line 172

Quoted 10 lines
lib/bpf/bpf_pkt.c +3 -1
 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++) {

After looking at the code more closely - it might not be that straightforward as I thought: apply_filter() groups 'good' packets consequently, and for 'bad' packets does one of two things:
a) if drop != 0, then it frees them out (RX filter)
b) if drop == 0, then it places them after 'good' packets (TX filter) While a) is simple to do with fixed length array (we just need read and write index), to support b) - we need to do something more awkward, i.e. - walk mb[] in backwards order, and/or keep copying bad packets to the end at each iteration.
All that looks quite complicated, so if for now you'll decide to switch back to alloca() based approach (first version of the patch) - I'll be ok with that.
Sorry for not spotting that earlier.
Konstantin

Quoted 126 lines
lib/bpf/bpf_pkt.c +43 -33
@@ -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;
 }
 

/*

lib/bpf/meson.build +0 -3
index 28df7f469a..aa258a9061 100644
@@ -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

#22 lore raw

v4 cover BPF enhancements

In reply to 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.

It is disappointing that there were no examples or tests when the code was submitted.

v4 - review feedback - use alloca instead of burst to remove dependency on VLA

v3 - do not depend on xxd -n flag which is not present on older versions

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: replace use of VLA

 app/test/bpf/filter.c    |  53 +++++
 app/test/bpf/load.c      |  62 ++++++
 app/test/bpf/meson.build |  53 +++++
 app/test/meson.build     |   2 +
 app/test/test_bpf.c      | 451 +++++++++++++++++++++++++++++++++++++++
 lib/bpf/bpf_pkt.c        |  15 +-
 lib/bpf/rte_bpf.h        |  11 +-
 7 files changed, 636 insertions(+), 11 deletions(-)
 create mode 100644 app/test/bpf/filter.c
 create mode 100644 app/test/bpf/load.c
 create mode 100644 app/test/bpf/meson.build
Signature · 1 line
2.51.0
#23 lore raw

PATCH v4 1/5 bpf: add allocation annotations to functions

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.

---
 lib/bpf/rte_bpf.h | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)
lib/bpf/rte_bpf.h +8 -3
index 80ebb0210f..309d84bc51 100644
@@ -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
 }
Signature · 1 line
2.51.0
Reviewed-by on v4 1/5
#24 lore raw

RE: [PATCH v4 1/5] bpf: add allocation annotations to functions

Quoted 63 lines

-----Original Message-----
From: Stephen Hemminger <[email protected]>
Sent: Tuesday 4 November 2025 16:07
To: [email protected]
Cc: Stephen Hemminger <[email protected]>; Konstantin Ananyev <[email protected]>
Subject: [PATCH v4 1/5] bpf: add allocation annotations to functions

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.

---
 lib/bpf/rte_bpf.h | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)
lib/bpf/rte_bpf.h +8 -4
index 80ebb0210f..309d84bc51 100644
@@ -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
 }
--
Quoted 1 line

2.51.0

#25 lore raw

PATCH v4 2/5 bpf: use bulk free on filtered packets

In reply to Stephen Hemminger

When rte_pktmbuf_free_bulk is faster because it does single mempool operation rather than per-packet.

---
 lib/bpf/bpf_pkt.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)
lib/bpf/bpf_pkt.c +1 -2
index 01f813c56b..087ac0f244 100644
@@ -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++)
Signature · 1 line
2.51.0
Acked-by on v4 2/5
#26 lore raw

RE: [PATCH v4 2/5] bpf: use bulk free on filtered packets

Quoted 24 lines

When rte_pktmbuf_free_bulk is faster because it does single mempool operation rather than per-packet.

---
 lib/bpf/bpf_pkt.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)
lib/bpf/bpf_pkt.c +1 -2
index 01f813c56b..087ac0f244 100644
@@ -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 */
Quoted 2 lines

for (i = 0; i != k; i++)
--

Quoted 1 line

2.51.0

Reviewed-by on v4 2/5
#27 lore raw

RE: [PATCH v4 2/5] bpf: use bulk free on filtered packets

In reply to Stephen Hemminger
Quoted 30 lines

-----Original Message-----
From: Stephen Hemminger <[email protected]>
Sent: Tuesday 4 November 2025 16:07
To: [email protected]
Cc: Stephen Hemminger <[email protected]>; Konstantin Ananyev <[email protected]>
Subject: [PATCH v4 2/5] bpf: use bulk free on filtered packets

When rte_pktmbuf_free_bulk is faster because it does single mempool operation rather than per-packet.

---
 lib/bpf/bpf_pkt.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)
lib/bpf/bpf_pkt.c +1 -3
index 01f813c56b..087ac0f244 100644
@@ -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++)
--
Quoted 1 line

2.51.0

#28 lore raw

PATCH v4 3/5 bpf: add a test for BPF ELF load

In reply to Stephen Hemminger

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.

---
 app/test/bpf/load.c      |  62 +++++++++++++++++
 app/test/bpf/meson.build |  52 ++++++++++++++
 app/test/meson.build     |   2 +
 app/test/test_bpf.c      | 147 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 263 insertions(+)
 create mode 100644 app/test/bpf/load.c
 create mode 100644 app/test/bpf/meson.build
app/test/bpf/load.c new file +62 -0
new file mode 100644
index 0000000000..9678c110d9
@@ -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;
+}
app/test/bpf/meson.build new file +52 -0

52 lines not on this page · raw message

app/test/meson.build +2 -0
index 8df8d3edd1..efec42a6bf 100644
@@ -281,6 +281,8 @@ if not is_windows
             install: false)
 endif
 
+subdir('bpf')
+
 subdir('test_cfgfiles')
 
 resources += test_cfgfile_h
app/test/test_bpf.c +147 -0

165 lines not on this page · raw message

Signature · 1 line
2.51.0
#29 lore raw

RE: [PATCH v4 3/5] bpf: add a test for BPF ELF load

Thank you for doing this! See comments inline.

Quoted 40 lines

-----Original Message-----
From: Stephen Hemminger <[email protected]>
Sent: Tuesday 4 November 2025 16:07
To: [email protected]
Cc: Stephen Hemminger <[email protected]>; Konstantin Ananyev <[email protected]>
Subject: [PATCH v4 3/5] bpf: add a test for BPF ELF load

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.

---
 app/test/bpf/load.c      |  62 +++++++++++++++++
 app/test/bpf/meson.build |  52 ++++++++++++++
 app/test/meson.build     |   2 +
 app/test/test_bpf.c      | 147 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 263 insertions(+)
 create mode 100644 app/test/bpf/load.c
 create mode 100644 app/test/bpf/meson.build
app/test/bpf/load.c new file +11 -0
new file mode 100644
index 0000000000..9678c110d9
@@ -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 {

app/test/bpf/load.c line 21

Quoted 10 lines
app/test/bpf/load.c new file +10 -0
+	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];
+};

Nit: I am sympathetic to re-using types already defined in test_bpf.c, but we don't really need 8 elements, and first two members in dummy_offset are no longer atomic which may or may not create problems someday. Perhaps it would be easier and less confusing to just define another type for this test.

app/test/bpf/load.c line 24

Quoted 3 lines
app/test/bpf/load.c new file +3 -0
+
+/* External function declaration - provided by test via xsym */
+extern void dummy_func1(const void *p, uint32_t *v32, uint64_t *v64);

Any reason not to make first argument struct dummy_vect8? Not that it mattered for the test, just creates an impression that something is not working or not trivial here.

app/test/bpf/load.c line 34

Quoted 10 lines
app/test/bpf/load.c new file +10 -0
+
+/*
+ * 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))

Do we need used here and in other section attributes? I do not thing clang -c will eliminate non-static/inline functions from the object file, or maybe I don't understand the purpose (perhaps needs a comment).

Quoted 18 lines
app/test/bpf/load.c new file +18 -0
+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;
+

app/test/bpf/load.c line 62

Quoted 10 lines
app/test/bpf/load.c new file +10 -0
+	/* 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;
+}

// snip meson files, lgtm

Quoted 22 lines
app/test/test_bpf.c +3 -0
@@ -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,150 @@ test_bpf(void)
 
 REGISTER_FAST_TEST(bpf_autotest, true, true, test_bpf);

app/test/test_bpf.c test_bpf(void) · line 3292

Quoted 10 lines
app/test/test_bpf.c +9 -0
 
+#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)

Nit: last argument is not technically a suffix.

app/test/test_bpf.c test_bpf(void) · line 3298

Quoted 6 lines
app/test/test_bpf.c +6 -0
+{
+	char *tmpfile = NULL;
+	int fd;
+	ssize_t written;
+
+	if (asprintf(&tmpfile, "/tmp/dpdk_bpf_%s_XXXXXX", suffix) < 0) {

Not sure what the actual standard is, but I found in some previous discussions a suggestion to use $TMPDIR and only fall back to /tmp if it is not defined.

Also, maybe give it .o suffix and use mkstemps below?

Quoted 25 lines
app/test/test_bpf.c +25 -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;
+	}

app/test/test_bpf.c test_bpf(void) · line 3333

Quoted 10 lines
app/test/test_bpf.c +10 -0
+
+	return tmpfile;
+}
+
+#include "test_bpf_load.h"
+
+static int
+test_bpf_elf_load(void)
+{
+	uint8_t tbuf[sizeof(struct dummy_vect8)];

Why do we have to use unit8_t array here instead of just struct dummy_vect8?

If we do have to, how is alignment ensured?

Quoted 47 lines
app/test/test_bpf.c +47 -0
+	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(app_test_bpf_load_o,
+					     app_test_bpf_load_o_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 */

app/test/test_bpf.c test_bpf(void) · line 3390

Quoted 10 lines
app/test/test_bpf.c +10 -0
+	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);

Could we be more specific with the value we expect?

Quoted 10 lines
app/test/test_bpf.c +10 -0
+
+	/* 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;

app/test/test_bpf.c test_bpf(void) · line 3410

Quoted 10 lines
app/test/test_bpf.c +10 -0
+		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);

If any of the above asserts fail, leak sanitizer will also be unhappy. Perhaps we could unlink tmpfile just after the rte_bpf_elf_load call, and unload BPF before verifying return values from calls.

Quoted 22 lines
app/test/test_bpf.c +17 -1
+
+	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

#30 lore raw

RE: [PATCH v4 3/5] bpf: add a test for BPF ELF load

P.S. When running with UB sanitizer:

RTE>>bpf_elf_load_autotest
BPF: resolve_xsym(40): EBPF_PSEUDO_CALL to external function: dummy_func1
../../src/lib/bpf/bpf_validate.c:247:24: runtime error: signed integer overflow: 9223372036854775807 + 2147483647 cannot be represented in type 'long int'

    #0 0x2b1fdca in eval_add ../../src/lib/bpf/bpf_validate.c:247
    #1 0x2b2b9dc in eval_alu ../../src/lib/bpf/bpf_validate.c:691
    #2 0x2b3b187 in evaluate ../../src/lib/bpf/bpf_validate.c:2446
    #3 0x2b3bf0f in __rte_bpf_validate ../../src/lib/bpf/bpf_validate.c:2537
    #4 0x2b19b33 in rte_bpf_load ../../src/lib/bpf/bpf_load.c:113
    #5 0x2b47d6d in bpf_load_elf ../../src/lib/bpf/bpf_load_elf.c:288
    #6 0x2b47fe8 in rte_bpf_elf_load ../../src/lib/bpf/bpf_load_elf.c:320
    #7 0x66b4b8 in test_bpf_elf_load ../../src/app/test/test_bpf.c:3382
#31 lore raw

Re: [PATCH v4 3/5] bpf: add a test for BPF ELF load

Quoted 5 lines · On Fri, 7 Nov 2025 17:45:51 +0000 Marat Khalili <[email protected]>…

P.S. When running with UB sanitizer:

RTE>>bpf_elf_load_autotest
BPF: resolve_xsym(40): EBPF_PSEUDO_CALL to external function: dummy_func1
../../src/lib/bpf/bpf_validate.c:247:24: runtime error: signed integer overflow: 9223372036854775807 + 2147483647 cannot be represented in type 'long int'

Quoted 8 lines
    #0 0x2b1fdca in eval_add ../../src/lib/bpf/bpf_validate.c:247
    #1 0x2b2b9dc in eval_alu ../../src/lib/bpf/bpf_validate.c:691
    #2 0x2b3b187 in evaluate ../../src/lib/bpf/bpf_validate.c:2446
    #3 0x2b3bf0f in __rte_bpf_validate ../../src/lib/bpf/bpf_validate.c:2537
    #4 0x2b19b33 in rte_bpf_load ../../src/lib/bpf/bpf_load.c:113
    #5 0x2b47d6d in bpf_load_elf ../../src/lib/bpf/bpf_load_elf.c:288
    #6 0x2b47fe8 in rte_bpf_elf_load ../../src/lib/bpf/bpf_load_elf.c:320
    #7 0x66b4b8 in test_bpf_elf_load ../../src/app/test/test_bpf.c:3382

Hmm. looks like a pre-existing BPF library bug since this code was never tested before.

#32 lore raw

RE: [PATCH v4 3/5] bpf: add a test for BPF ELF load

Quoted 13 lines
Quoted 3 lines

RTE>>bpf_elf_load_autotest
BPF: resolve_xsym(40): EBPF_PSEUDO_CALL to external function: dummy_func1 ../../src/lib/bpf/bpf_validate.c:247:24: runtime error: signed integer overflow: 9223372036854775807

+ 2147483647 cannot be represented in type 'long int'

Quoted 8 lines
    #0 0x2b1fdca in eval_add ../../src/lib/bpf/bpf_validate.c:247
    #1 0x2b2b9dc in eval_alu ../../src/lib/bpf/bpf_validate.c:691
    #2 0x2b3b187 in evaluate ../../src/lib/bpf/bpf_validate.c:2446
    #3 0x2b3bf0f in __rte_bpf_validate ../../src/lib/bpf/bpf_validate.c:2537
    #4 0x2b19b33 in rte_bpf_load ../../src/lib/bpf/bpf_load.c:113
    #5 0x2b47d6d in bpf_load_elf ../../src/lib/bpf/bpf_load_elf.c:288
    #6 0x2b47fe8 in rte_bpf_elf_load ../../src/lib/bpf/bpf_load_elf.c:320
    #7 0x66b4b8 in test_bpf_elf_load ../../src/app/test/test_bpf.c:3382
Quoted 2 lines

Hmm. looks like a pre-existing BPF library bug since this code was never tested before.

Yes, that's correct. Sent some fixes for this and some other issues to the mailing list just now, maybe we should treat these problems separately.

#33 lore raw

Re: [PATCH v4 3/5] bpf: add a test for BPF ELF load

In reply to Marat Khalili
Quoted 14 lines · On Fri, 7 Nov 2025 17:33:08 +0000 Marat Khalili <[email protected]>…

app/test/bpf/load.c line 34

Quoted 9 lines
app/test/bpf/load.c new file +9 -0
+/*
+ * 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))  

Do we need used here and in other section attributes? I do not thing clang -c will eliminate non-static/inline functions from the object file, or maybe I don't understand the purpose (perhaps needs a comment).

Quoted 2 lines

app/test/bpf/load.c line 36

Quoted 2 lines
app/test/bpf/load.c new file +2 -0
+uint64_t
+test_func(struct dummy_vect8 *arg)

The BPF loader in DPDK uses section names as the way to find functions. The original version of this code was autogenerated off of examples used in kernel BPF.

#34 lore raw

PATCH v4 4/5 bpf: add test for Rx and Tx filtering

In reply to Stephen Hemminger

New test using null device to test filtering with BPF.

---
 app/test/bpf/filter.c    |  53 +++++++
 app/test/bpf/meson.build |   1 +
 app/test/test_bpf.c      | 304 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 358 insertions(+)
 create mode 100644 app/test/bpf/filter.c
app/test/bpf/filter.c new file +53 -0
new file mode 100644
index 0000000000..d47233a47a
@@ -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;
+}
app/test/bpf/meson.build +1 -0
index b4f54aa976..19fec05521 100644
@@ -32,6 +32,7 @@ cflags += '-DTEST_BPF_ELF_LOAD'
 # BPF sources to compile
 bpf_progs = {
     'load' : 'test_bpf_load',
+    'filter' : 'test_bpf_filter',
 }
 
 foreach bpf_src, bpf_hdr : bpf_progs
app/test/test_bpf.c +304 -0

325 lines not on this page · raw message

Signature · 1 line
2.51.0
#35 lore raw

RE: [PATCH v4 4/5] bpf: add test for Rx and Tx filtering

Thank you for doing this! See comments below.

Quoted 34 lines

-----Original Message-----
From: Stephen Hemminger <[email protected]>
Sent: Tuesday 4 November 2025 16:07
To: [email protected]
Cc: Stephen Hemminger <[email protected]>; Konstantin Ananyev <[email protected]>
Subject: [PATCH v4 4/5] bpf: add test for Rx and Tx filtering

New test using null device to test filtering with BPF.

---
 app/test/bpf/filter.c    |  53 +++++++
 app/test/bpf/meson.build |   1 +
 app/test/test_bpf.c      | 304 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 358 insertions(+)
 create mode 100644 app/test/bpf/filter.c
app/test/bpf/filter.c new file +11 -0
new file mode 100644
index 0000000000..d47233a47a
@@ -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

app/test/bpf/filter.c line 21

Quoted 10 lines
app/test/bpf/filter.c new file +10 -0
+ *
+ * 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))

Could this section (and function) be called better, e.g. filter_ip_tcp?

app/test/bpf/filter.c line 25

Quoted 4 lines
app/test/bpf/filter.c new file +4 -0
+uint64_t
+test_filter(void *pkt)
+{
+	uint8_t *data = pkt;

Does pkt point to packet data or an mbuf? I would think we need mbuf functions here to access data. Not sure why the test is not failing though, maybe I misunderstand it.

Quoted 59 lines
app/test/bpf/filter.c new file +28 -0
+
+	/* 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;
+}
app/test/bpf/meson.build +1 -0
index b4f54aa976..19fec05521 100644
@@ -32,6 +32,7 @@ cflags += '-DTEST_BPF_ELF_LOAD'
 # BPF sources to compile
 bpf_progs = {
     'load' : 'test_bpf_load',
+    'filter' : 'test_bpf_filter',
 }
 
 foreach bpf_src, bpf_hdr : bpf_progs
app/test/test_bpf.c +6 -0
index ec84b85f1c..54b6330678 100644
@@ -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"
 
@@ -3412,6 +3418,288 @@ test_bpf_elf_load(void)
 	printf("%s: ELF load test passed\n", __func__);

app/test/test_bpf.c test_bpf_elf_load(void) · line 3428

Quoted 10 lines
app/test/test_bpf.c +8 -0
 	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)

Nit: The function type should be on a line by itself preceding the function (also in other places).

// snip packets setup functions

app/test/test_bpf.c test_bpf_elf_load(void) · line 3517

Quoted 2 lines
app/test/test_bpf.c +2 -0
+static int bpf_tx_test(uint16_t port, const char *tmpfile, struct rte_mempool *pool,
+		       const char *fname, uint32_t flags)

Nit: not sure fname is a good name for ELF section.

app/test/test_bpf.c test_bpf_elf_load(void) · line 3521

Quoted 4 lines
app/test/test_bpf.c +4 -0
+{
+	const struct rte_bpf_prm prm = {
+		.prog_arg = {
+			.type = RTE_BPF_ARG_PTR,

Should this be RTE_BPF_ARG_PTR_MBUF and include buf_size?

Quoted 21 lines
app/test/test_bpf.c +21 -0
+			.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 */

app/test/test_bpf.c test_bpf_elf_load(void) · line 3552

Quoted 10 lines
app/test/test_bpf.c +10 -0
+	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;
+}

// snip test_bpf_elf_tx_load

app/test/test_bpf.c test_bpf_elf_load(void) · line 3604

Quoted 2 lines
app/test/test_bpf.c +2 -0
+static int bpf_rx_test(uint16_t port, const char *tmpfile, struct rte_mempool *pool,
+		       const char *fname, uint32_t flags, uint16_t expected)

Is rx test significantly different from tx one? Maybe add a comment what each test does?

// snip the rest, lgtm

#36 lore raw

Re: [PATCH v4 4/5] bpf: add test for Rx and Tx filtering

Quoted 49 lines · On Fri, 7 Nov 2025 17:30:51 +0000 Marat Khalili <[email protected]>…

Thank you for doing this! See comments below.

Quoted 34 lines

-----Original Message-----
From: Stephen Hemminger <[email protected]>
Sent: Tuesday 4 November 2025 16:07
To: [email protected]
Cc: Stephen Hemminger <[email protected]>; Konstantin Ananyev <[email protected]>
Subject: [PATCH v4 4/5] bpf: add test for Rx and Tx filtering

New test using null device to test filtering with BPF.

---
 app/test/bpf/filter.c    |  53 +++++++
 app/test/bpf/meson.build |   1 +
 app/test/test_bpf.c      | 304 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 358 insertions(+)
 create mode 100644 app/test/bpf/filter.c
app/test/bpf/filter.c new file +11 -0
new file mode 100644
index 0000000000..d47233a47a
@@ -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

app/test/bpf/filter.c line 21

Quoted 10 lines
app/test/bpf/filter.c new file +10 -0
+ *
+ * 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))  

Could this section (and function) be called better, e.g. filter_ip_tcp?

Quoted 8 lines

app/test/bpf/filter.c line 25

Quoted 4 lines
app/test/bpf/filter.c new file +4 -0
+uint64_t
+test_filter(void *pkt)
+{
+	uint8_t *data = pkt;  

Does pkt point to packet data or an mbuf? I would think we need mbuf functions here to access data. Not sure why the test is not failing though, maybe I misunderstand it.

The API for DPDK BPF gives option for packet data or mbuf. See rte_bpf_arg_type.

This test uses packet data. Mostly because it is easier, using mbuf requires more overhead and BPF validator often doesn't like it.

#37 lore raw

RE: [PATCH v4 4/5] bpf: add test for Rx and Tx filtering

Quoted 9 lines
Quoted 3 lines

Does pkt point to packet data or an mbuf? I would think we need mbuf functions here to access data. Not sure why the test is not failing though, maybe I misunderstand it.

The API for DPDK BPF gives option for packet data or mbuf. See rte_bpf_arg_type.

This test uses packet data. Mostly because it is easier, using mbuf requires more overhead and BPF validator often doesn't like it.

Oh yes, I missed the bits in bpf_pkt.c that actually look at this and call rte_pktmbuf_mtod if necessary.

#38 lore raw

PATCH v4 5/5 bpf: replace use of VLA

In reply to Stephen Hemminger

Variable length arrays are extension not required in C standard and not available in Windows.
Use alloca() instead of variable length array.

---
 lib/bpf/bpf_pkt.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)
lib/bpf/bpf_pkt.c +6 -6
index 087ac0f244..5007f6aef5 100644
@@ -163,7 +163,7 @@ 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 = alloca(num * sizeof(struct rte_mbuf *));
 
 	for (i = 0, j = 0, k = 0; i != num; i++) {
 
@@ -192,8 +192,8 @@ 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];
+	void **dp = alloca(num * sizeof(void *));
+	uint64_t *rc = alloca(num * sizeof(uint64_t));
 
 	for (i = 0; i != num; i++)
 		dp[i] = rte_pktmbuf_mtod(mb[i], void *);
@@ -208,7 +208,7 @@ pkt_filter_jit(const struct rte_bpf_jit *jit, struct rte_mbuf *mb[],
 {
 	uint32_t i, n;
 	void *dp;
-	uint64_t rc[num];
+	uint64_t *rc = alloca(num * sizeof(uint64_t));
 
 	n = 0;
 	for (i = 0; i != num; i++) {
@@ -227,7 +227,7 @@ 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];
+	uint64_t *rc = alloca(num * sizeof(uint64_t));
 
 	rte_bpf_exec_burst(bpf, (void **)mb, rc, num);
 	return apply_filter(mb, rc, num, drop);
@@ -238,7 +238,7 @@ 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];
+	uint64_t *rc = alloca(num * sizeof(uint64_t));
 
 	n = 0;
 	for (i = 0; i != num; i++) {
Signature · 1 line
2.51.0
Acked-by on v4 5/5
#39 lore raw

RE: [PATCH v4 5/5] bpf: replace use of VLA

Quoted 65 lines

Variable length arrays are extension not required in C standard and not available in Windows.
Use alloca() instead of variable length array.

---
 lib/bpf/bpf_pkt.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)
lib/bpf/bpf_pkt.c +3 -3
index 087ac0f244..5007f6aef5 100644
@@ -163,7 +163,7 @@ 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 = alloca(num * sizeof(struct rte_mbuf *));
 
 	for (i = 0, j = 0, k = 0; i != num; i++) {
@@ -192,8 +192,8 @@ 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];
+	void **dp = alloca(num * sizeof(void *));
+	uint64_t *rc = alloca(num * sizeof(uint64_t));
 
 	for (i = 0; i != num; i++)
lib/bpf/bpf_pkt.c +1 -1
 		dp[i] = rte_pktmbuf_mtod(mb[i], void *);
@@ -208,7 +208,7 @@ pkt_filter_jit(const struct rte_bpf_jit *jit, struct rte_mbuf
 *mb[],
 {
 	uint32_t i, n;
 	void *dp;
-	uint64_t rc[num];
+	uint64_t *rc = alloca(num * sizeof(uint64_t));
 
 	n = 0;
lib/bpf/bpf_pkt.c +2 -2
 	for (i = 0; i != num; i++) {
@@ -227,7 +227,7 @@ 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];
+	uint64_t *rc = alloca(num * sizeof(uint64_t));
 
 	rte_bpf_exec_burst(bpf, (void **)mb, rc, num);
 	return apply_filter(mb, rc, num, drop);
@@ -238,7 +238,7 @@ 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];
+	uint64_t *rc = alloca(num * sizeof(uint64_t));
 
 	n = 0;
Quoted 2 lines

for (i = 0; i != num; i++) {
--

Quoted 1 line

2.51.0

Reviewed-by on v4 5/5
#40 lore raw

RE: [PATCH v4 5/5] bpf: replace use of VLA

In reply to Stephen Hemminger
Quoted 68 lines

-----Original Message-----
From: Stephen Hemminger <[email protected]>
Sent: Tuesday 4 November 2025 16:08
To: [email protected]
Cc: Stephen Hemminger <[email protected]>; Konstantin Ananyev <[email protected]>
Subject: [PATCH v4 5/5] bpf: replace use of VLA

Variable length arrays are extension not required in C standard and not available in Windows.
Use alloca() instead of variable length array.

---
 lib/bpf/bpf_pkt.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)
lib/bpf/bpf_pkt.c +6 -7
index 087ac0f244..5007f6aef5 100644
@@ -163,7 +163,7 @@ 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 = alloca(num * sizeof(struct rte_mbuf *));
 
 	for (i = 0, j = 0, k = 0; i != num; i++) {
 
@@ -192,8 +192,8 @@ 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];
+	void **dp = alloca(num * sizeof(void *));
+	uint64_t *rc = alloca(num * sizeof(uint64_t));
 
 	for (i = 0; i != num; i++)
 		dp[i] = rte_pktmbuf_mtod(mb[i], void *);
@@ -208,7 +208,7 @@ pkt_filter_jit(const struct rte_bpf_jit *jit, struct rte_mbuf *mb[],
 {
 	uint32_t i, n;
 	void *dp;
-	uint64_t rc[num];
+	uint64_t *rc = alloca(num * sizeof(uint64_t));
 
 	n = 0;
 	for (i = 0; i != num; i++) {
@@ -227,7 +227,7 @@ 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];
+	uint64_t *rc = alloca(num * sizeof(uint64_t));
 
 	rte_bpf_exec_burst(bpf, (void **)mb, rc, num);
 	return apply_filter(mb, rc, num, drop);
@@ -238,7 +238,7 @@ 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];
+	uint64_t *rc = alloca(num * sizeof(uint64_t));
 
 	n = 0;
 	for (i = 0; i != num; i++) {
--
Quoted 1 line

2.51.0

#41 lore raw

v5 cover BPF cleanup and tests

In reply to 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.

It is disappointing that there were no examples or tests when the code was submitted.

v5 - review feedback
make it clear that load test is mirroring previous test and check the result.

v4 - review feedback - use alloca instead of burst to remove dependency on VLA

v3 - do not depend on xxd -n flag which is not present on older versions

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: replace use of VLA

 app/test/bpf/filter.c    |  53 +++++
 app/test/bpf/load.c      |  51 +++++
 app/test/bpf/meson.build |  53 +++++
 app/test/meson.build     |   2 +
 app/test/test_bpf.c      | 463 +++++++++++++++++++++++++++++++++++++++
 lib/bpf/bpf_pkt.c        |  15 +-
 lib/bpf/rte_bpf.h        |  11 +-
 7 files changed, 637 insertions(+), 11 deletions(-)
 create mode 100644 app/test/bpf/filter.c
 create mode 100644 app/test/bpf/load.c
 create mode 100644 app/test/bpf/meson.build
Signature · 1 line
2.51.0
#42 lore raw

PATCH v5 1/5 bpf: add allocation annotations to functions

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.

---
 lib/bpf/rte_bpf.h | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)
lib/bpf/rte_bpf.h +8 -3
index 80ebb0210f..309d84bc51 100644
@@ -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
 }
Signature · 1 line
2.51.0
#43 lore raw

PATCH v5 2/5 bpf: use bulk free on filtered packets

In reply to Stephen Hemminger

When rte_pktmbuf_free_bulk is faster because it does single mempool operation rather than per-packet.

---
 lib/bpf/bpf_pkt.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)
lib/bpf/bpf_pkt.c +1 -2
index 01f813c56b..087ac0f244 100644
@@ -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++)
Signature · 1 line
2.51.0
#44 lore raw

PATCH v5 3/5 bpf: add a test for BPF ELF load

In reply to Stephen Hemminger

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.

---
 app/test/bpf/load.c      |  51 +++++++++++++
 app/test/bpf/meson.build |  52 +++++++++++++
 app/test/meson.build     |   2 +
 app/test/test_bpf.c      | 156 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 261 insertions(+)
 create mode 100644 app/test/bpf/load.c
 create mode 100644 app/test/bpf/meson.build
app/test/bpf/load.c new file +51 -0
new file mode 100644
index 0000000000..a4d3d61d7a
@@ -0,0 +1,51 @@
+/* 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 is compiled version of code used in test_call1
+ */
+__attribute__((section("call1"), used))
+uint64_t
+test_call1(struct dummy_vect8 *arg)
+{
+	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;
+
+	v64 += v32;
+	return v64;
+}
app/test/bpf/meson.build new file +52 -0

52 lines not on this page · raw message

app/test/meson.build +2 -0
index 8df8d3edd1..efec42a6bf 100644
@@ -281,6 +281,8 @@ if not is_windows
             install: false)
 endif
 
+subdir('bpf')
+
 subdir('test_cfgfiles')
 
 resources += test_cfgfile_h
app/test/test_bpf.c +156 -0

174 lines not on this page · raw message

Signature · 1 line
2.51.0
#45 lore raw

RE: [PATCH v5 3/5] bpf: add a test for BPF ELF load

Quoted 26 lines
app/test/bpf/load.c new file +20 -0
new file mode 100644
index 0000000000..a4d3d61d7a
@@ -0,0 +1,51 @@
+/* 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];

app/test/bpf/load.c line 30

Quoted 10 lines
app/test/bpf/load.c new file +10 -0
+	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 is compiled version of code used in test_call1
+ */

Thank you for this clarification, now it makes more sense why we use these structs.

// snip

Quoted 26 lines
app/test/bpf/meson.build new file +20 -0
new file mode 100644
index 0000000000..b4f54aa976
@@ -0,0 +1,52 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright 2025 Stephen Hemminger <[email protected]>
+
+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)

app/test/bpf/meson.build line 30

Quoted 10 lines
app/test/bpf/meson.build new file +10 -0
+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'

Sorry for not noticing it earlier, we probably want these tests depend on RTE_LIBRTE_BPF_ELF, otherwise they fail when libelf is not installed.

// snip

app/test/bpf/meson.build line 35

Quoted 5 lines
app/test/bpf/meson.build new file +5 -0
+static int
+test_bpf_elf_load(void)
+{
+	static const char test_section[] = "call1";
+	uint8_t tbuf[sizeof(struct dummy_vect8)];

I still think alignment may become an issue here.

#46 lore raw

Re: [PATCH v5 3/5] bpf: add a test for BPF ELF load

Quoted 6 lines · On Mon, 10 Nov 2025 16:38:08 +0000 Marat Khalili <[email protected]>…
Quoted 6 lines
app/test/bpf/meson.build new file +6 -0
+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
+
Quoted 8 lines

app/test/bpf/meson.build line 30

Quoted 5 lines
app/test/bpf/meson.build new file +5 -0
+# BPF compiler flags
+bpf_cflags = [ '-O2', '-target', 'bpf', '-g', '-c']
+
+# Enable test in test_bpf.c
+cflags += '-DTEST_BPF_ELF_LOAD'  

Sorry for not noticing it earlier, we probably want these tests depend on RTE_LIBRTE_BPF_ELF, otherwise they fail when libelf is not installed.

I tested without elf and it build ok.

Quoted 9 lines

// snip

app/test/test_bpf.c test_bpf(void) · line 3338

Quoted 5 lines
app/test/test_bpf.c +5 -0
+static int
+test_bpf_elf_load(void)
+{
+	static const char test_section[] = "call1";
+	uint8_t tbuf[sizeof(struct dummy_vect8)];  

I still think alignment may become an issue here.

On x86 alignment doesn't matter. But would be better to use a union to be safe.

#47 lore raw

RE: [PATCH v5 3/5] bpf: add a test for BPF ELF load

Quoted 4 lines
Quoted 2 lines

Sorry for not noticing it earlier, we probably want these tests depend on RTE_LIBRTE_BPF_ELF, otherwise they fail when libelf is not installed.

I tested without elf and it build ok.

Strange, meson tests fail here...

#48 lore raw

Re: [PATCH v5 3/5] bpf: add a test for BPF ELF load

In reply to Marat Khalili
Quoted 7 lines · On Mon, 10 Nov 2025 16:38:08 +0000 Marat Khalili <[email protected]>…

app/test/test_bpf.c test_bpf(void) · line 3338

Quoted 5 lines
app/test/test_bpf.c +5 -0
+static int
+test_bpf_elf_load(void)
+{
+	static const char test_section[] = "call1";
+	uint8_t tbuf[sizeof(struct dummy_vect8)];  

I still think alignment may become an issue here.

This was copied from earlier part (run_test).

Acked-by on v5 3/5
#49 lore raw

RE: [PATCH v5 3/5] bpf: add a test for BPF ELF load

In reply to Stephen Hemminger
Quoted 7 lines

-----Original Message-----
From: Stephen Hemminger <[email protected]>
Sent: Sunday 9 November 2025 20:08
To: [email protected]
Cc: Stephen Hemminger <[email protected]>; Konstantin Ananyev <[email protected]>
Subject: [PATCH v5 3/5] bpf: add a test for BPF ELF load

Quoted 7 lines

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.

Don't know what's going on with the test without libelf, perhaps someone else needs to try.

Apart from this,

Quoted 320 lines
---
 app/test/bpf/load.c      |  51 +++++++++++++
 app/test/bpf/meson.build |  52 +++++++++++++
 app/test/meson.build     |   2 +
 app/test/test_bpf.c      | 156 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 261 insertions(+)
 create mode 100644 app/test/bpf/load.c
 create mode 100644 app/test/bpf/meson.build
app/test/bpf/load.c new file +51 -0
new file mode 100644
index 0000000000..a4d3d61d7a
@@ -0,0 +1,51 @@
+/* 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 is compiled version of code used in test_call1
+ */
+__attribute__((section("call1"), used))
+uint64_t
+test_call1(struct dummy_vect8 *arg)
+{
+	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;
+
+	v64 += v32;
+	return v64;
+}
app/test/bpf/meson.build new file +52 -0
new file mode 100644
index 0000000000..b4f54aa976
@@ -0,0 +1,52 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright 2025 Stephen Hemminger <[email protected]>
+
+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
+bpf_progs = {
+    'load' : 'test_bpf_load',
+}
+
+foreach bpf_src, bpf_hdr : bpf_progs
+    # Compile BPF C source to object file
+    bpf_obj = custom_target(bpf_src + '_o',
+        input: bpf_src + '.c',
+        output: bpf_src + '.o',
+        command: [ clang, bpf_cflags, '@INPUT@', '-o', '@OUTPUT@'])
+
+    # Convert object file to C header using xxd
+    bpf_test_h = custom_target(bpf_src + '_h',
+        input: bpf_obj,
+        output: bpf_hdr + '.h',
+        command: [ xxd, '-i', '@INPUT@', '@OUTPUT@'])
+
+    resources += bpf_test_h
+
+endforeach
app/test/meson.build +2 -0
index 8df8d3edd1..efec42a6bf 100644
@@ -281,6 +281,8 @@ if not is_windows
             install: false)
 endif
 
+subdir('bpf')
+
 subdir('test_cfgfiles')
 
 resources += test_cfgfile_h
app/test/test_bpf.c +156 -1
index 90e10d7d2c..a80f41a543 100644
@@ -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,159 @@ 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 *name)
+{
+	char *tmpfile = NULL;
+	int fd;
+	ssize_t written;
+
+	if (asprintf(&tmpfile, "/tmp/dpdk_bpf_%s_XXXXXX.o", name) < 0) {
+		printf("%s@%d: asprintf failed: %s\n",
+		       __func__, __LINE__, strerror(errno));
+		return NULL;
+	}
+
+	/* Create and open temp file */
+	fd = mkstemps(tmpfile, strlen(".o"));
+	if (fd < 0) {
+		printf("%s@%d: mkstemps(%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"
+
+/*
+ * Test loading BPF program from an object file.
+ * This test uses same arguments as previous test_call1 example.
+ */
+static int
+test_bpf_elf_load(void)
+{
+	static const char test_section[] = "call1";
+	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(app_test_bpf_load_o,
+					     app_test_bpf_load_o_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, test_section);
+	unlink(tmpfile);
+	free(tmpfile);
+
+	TEST_ASSERT(bpf != NULL, "failed to load BPF %d:%s", 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 rc = rte_bpf_exec(bpf, tbuf);
+	ret = test_call1_check(rc, tbuf);
+	TEST_ASSERT(ret == 0, "test_call1_check failed: %d", ret);
+
+	/* 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;
+
+		rc = jit.func(tbuf);
+		ret = test_call1_check(rc, tbuf);
+		TEST_ASSERT(ret == 0, "jit test_call1_check failed: %d", ret);
+	}
+
+	rte_bpf_destroy(bpf);
+
+	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

#50 lore raw

PATCH v5 4/5 bpf: add test for Rx and Tx filtering

In reply to Stephen Hemminger

New test using null device to test filtering with BPF.

---
 app/test/bpf/filter.c    |  53 +++++++
 app/test/bpf/meson.build |   1 +
 app/test/test_bpf.c      | 307 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 361 insertions(+)
 create mode 100644 app/test/bpf/filter.c
app/test/bpf/filter.c new file +53 -0
new file mode 100644
index 0000000000..d47233a47a
@@ -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;
+}
app/test/bpf/meson.build +1 -0
index b4f54aa976..19fec05521 100644
@@ -32,6 +32,7 @@ cflags += '-DTEST_BPF_ELF_LOAD'
 # BPF sources to compile
 bpf_progs = {
     'load' : 'test_bpf_load',
+    'filter' : 'test_bpf_filter',
 }
 
 foreach bpf_src, bpf_hdr : bpf_progs
app/test/test_bpf.c +307 -0

328 lines not on this page · raw message

Signature · 1 line
2.51.0
Acked-by on v5 4/5
#51 lore raw

RE: [PATCH v5 4/5] bpf: add test for Rx and Tx filtering

Quoted 10 lines

-----Original Message-----
From: Stephen Hemminger <[email protected]>
Sent: Sunday 9 November 2025 20:08
To: [email protected]
Cc: Stephen Hemminger <[email protected]>; Konstantin Ananyev <[email protected]>
Subject: [PATCH v5 4/5] bpf: add test for Rx and Tx filtering

New test using null device to test filtering with BPF.

(Turns out there were some issues with the pre-existing code which I initially blamed on this patch set. Maybe some cleanup is needed.)

Quoted 415 lines
---
 app/test/bpf/filter.c    |  53 +++++++
 app/test/bpf/meson.build |   1 +
 app/test/test_bpf.c      | 307 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 361 insertions(+)
 create mode 100644 app/test/bpf/filter.c
app/test/bpf/filter.c new file +53 -0
new file mode 100644
index 0000000000..d47233a47a
@@ -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;
+}
app/test/bpf/meson.build +1 -0
index b4f54aa976..19fec05521 100644
@@ -32,6 +32,7 @@ cflags += '-DTEST_BPF_ELF_LOAD'
 # BPF sources to compile
 bpf_progs = {
     'load' : 'test_bpf_load',
+    'filter' : 'test_bpf_filter',
 }
 
 foreach bpf_src, bpf_hdr : bpf_progs
app/test/test_bpf.c +307 -1
index a80f41a543..24d18f9d33 100644
@@ -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"
 
@@ -3421,6 +3427,291 @@ 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 *section, 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, section, 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;
+}
+
+/* Test loading a transmit filter which only allows IPv4 packets */
+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(app_test_bpf_filter_o, app_test_bpf_filter_o_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;
+}
+
+/* Test loading a receive filter */
+static int bpf_rx_test(uint16_t port, const char *tmpfile, struct rte_mempool *pool,
+		       const char *section, 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, section, 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;
+}
+
+/* Test loading a receive filters, first with drop all and then with allow all packets */
+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(app_test_bpf_filter_o, app_test_bpf_filter_o_len, "rx");
+	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
@@ -3430,9 +3721,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

#52 lore raw

PATCH v5 5/5 bpf: replace use of VLA

In reply to Stephen Hemminger

Variable length arrays are extension not required in C standard and not available in Windows.
Use alloca() instead of variable length array.

---
 lib/bpf/bpf_pkt.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)
lib/bpf/bpf_pkt.c +6 -6
index 087ac0f244..5007f6aef5 100644
@@ -163,7 +163,7 @@ 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 = alloca(num * sizeof(struct rte_mbuf *));
 
 	for (i = 0, j = 0, k = 0; i != num; i++) {
 
@@ -192,8 +192,8 @@ 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];
+	void **dp = alloca(num * sizeof(void *));
+	uint64_t *rc = alloca(num * sizeof(uint64_t));
 
 	for (i = 0; i != num; i++)
 		dp[i] = rte_pktmbuf_mtod(mb[i], void *);
@@ -208,7 +208,7 @@ pkt_filter_jit(const struct rte_bpf_jit *jit, struct rte_mbuf *mb[],
 {
 	uint32_t i, n;
 	void *dp;
-	uint64_t rc[num];
+	uint64_t *rc = alloca(num * sizeof(uint64_t));
 
 	n = 0;
 	for (i = 0; i != num; i++) {
@@ -227,7 +227,7 @@ 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];
+	uint64_t *rc = alloca(num * sizeof(uint64_t));
 
 	rte_bpf_exec_burst(bpf, (void **)mb, rc, num);
 	return apply_filter(mb, rc, num, drop);
@@ -238,7 +238,7 @@ 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];
+	uint64_t *rc = alloca(num * sizeof(uint64_t));
 
 	n = 0;
 	for (i = 0; i != num; i++) {
Signature · 1 line
2.51.0
#53 lore raw

Re: [PATCH v5 0/5] BPF cleanup and tests

In reply to Stephen Hemminger

09/11/2025 21:07, Stephen Hemminger:

Quoted 14 lines

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.

It is disappointing that there were no examples or tests when the code was submitted.

v5 - review feedback
make it clear that load test is mirroring previous test and check the result.

v4 - review feedback - use alloca instead of burst to remove dependency on VLA

Quoted 8 lines

v3 - do not depend on xxd -n flag which is not present on older versions

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: replace use of VLA

I have these warnings:
app/test/test_bpf.c: duplicated include: rte_bpf.h app/test/test_bpf.c: duplicated include: rte_ether.h app/test/test_bpf.c: duplicated include: rte_ip.h

Given some issues are reported by Marat with the test, I apply the patches which do not add new testing for now.

Please send a new version of the test when all is resolved.

#54 lore raw

v6 cover BPF tests

In reply to Stephen Hemminger

Add missing tests for BPF file loading functions.
This is subset of earlier patch.

v6 - rebase to main
- make sure that -ENOTSUP is handled
- remove duplicate includes
- combine all of the elf tests into one test instance

Stephen Hemminger (2):
bpf: add a test for BPF ELF load
bpf: add test for Rx and Tx filtering

 app/test/bpf/filter.c    |  53 +++++
 app/test/bpf/load.c      |  51 +++++
 app/test/bpf/meson.build |  53 +++++
 app/test/meson.build     |   2 +
 app/test/test_bpf.c      | 476 +++++++++++++++++++++++++++++++++++++++
 5 files changed, 635 insertions(+)
 create mode 100644 app/test/bpf/filter.c
 create mode 100644 app/test/bpf/load.c
 create mode 100644 app/test/bpf/meson.build
Signature · 1 line
2.51.0
#55 lore raw

PATCH v6 1/2 bpf: add a test for BPF ELF load

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 libelf library is not available, then DPDK bpf will return -ENOTSUP to the test and the test will be skipped.

---
 app/test/bpf/load.c      |  51 +++++++++++++
 app/test/bpf/meson.build |  52 +++++++++++++
 app/test/meson.build     |   2 +
 app/test/test_bpf.c      | 159 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 264 insertions(+)
 create mode 100644 app/test/bpf/load.c
 create mode 100644 app/test/bpf/meson.build
app/test/bpf/load.c new file +51 -0
new file mode 100644
index 0000000000..a4d3d61d7a
@@ -0,0 +1,51 @@
+/* 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 is compiled version of code used in test_call1
+ */
+__attribute__((section("call1"), used))
+uint64_t
+test_call1(struct dummy_vect8 *arg)
+{
+	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;
+
+	v64 += v32;
+	return v64;
+}
app/test/bpf/meson.build new file +52 -0

52 lines not on this page · raw message

app/test/meson.build +2 -0
index 8df8d3edd1..efec42a6bf 100644
@@ -281,6 +281,8 @@ if not is_windows
             install: false)
 endif
 
+subdir('bpf')
+
 subdir('test_cfgfiles')
 
 resources += test_cfgfile_h
app/test/test_bpf.c +159 -0

177 lines not on this page · raw message

Signature · 1 line
2.51.0
Acked-by on v6 1/2
#56 lore raw

RE: [PATCH v6 1/2] bpf: add a test for BPF ELF load

Quoted 334 lines

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 libelf library is not available, then DPDK bpf will return -ENOTSUP to the test and the test will be skipped.

---
 app/test/bpf/load.c      |  51 +++++++++++++
 app/test/bpf/meson.build |  52 +++++++++++++
 app/test/meson.build     |   2 +
 app/test/test_bpf.c      | 159 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 264 insertions(+)
 create mode 100644 app/test/bpf/load.c
 create mode 100644 app/test/bpf/meson.build
app/test/bpf/load.c new file +51 -0
new file mode 100644
index 0000000000..a4d3d61d7a
@@ -0,0 +1,51 @@
+/* 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 is compiled version of code used in test_call1
+ */
+__attribute__((section("call1"), used))
+uint64_t
+test_call1(struct dummy_vect8 *arg)
+{
+	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;
+
+	v64 += v32;
+	return v64;
+}
app/test/bpf/meson.build new file +10 -0
new file mode 100644
index 0000000000..b4f54aa976
@@ -0,0 +1,52 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright 2025 Stephen Hemminger <[email protected]>
+
+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',

app/test/bpf/meson.build new file +42 -0
+                                     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
+bpf_progs = {
+    'load' : 'test_bpf_load',
+}
+
+foreach bpf_src, bpf_hdr : bpf_progs
+    # Compile BPF C source to object file
+    bpf_obj = custom_target(bpf_src + '_o',
+        input: bpf_src + '.c',
+        output: bpf_src + '.o',
+        command: [ clang, bpf_cflags, '@INPUT@', '-o', '@OUTPUT@'])
+
+    # Convert object file to C header using xxd
+    bpf_test_h = custom_target(bpf_src + '_h',
+        input: bpf_obj,
+        output: bpf_hdr + '.h',
+        command: [ xxd, '-i', '@INPUT@', '@OUTPUT@'])
+
+    resources += bpf_test_h
+
+endforeach
app/test/meson.build +2 -0
index 8df8d3edd1..efec42a6bf 100644
@@ -281,6 +281,8 @@ if not is_windows
             install: false)
 endif
 
+subdir('bpf')
+
 subdir('test_cfgfiles')
 
 resources += test_cfgfile_h
app/test/test_bpf.c +159 -0
index 90e10d7d2c..c460002358 100644
@@ -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,7 @@
 #include <rte_random.h>
 #include <rte_byteorder.h>
 #include <rte_errno.h>
+
 #include "test.h"
 
 #if !defined(RTE_LIB_BPF)
@@ -3278,6 +3280,163 @@ 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 *name)
+{
+	char *tmpfile = NULL;
+	int fd;
+	ssize_t written;
+
+	if (asprintf(&tmpfile, "/tmp/dpdk_bpf_%s_XXXXXX.o", name) < 0) {
+		printf("%s@%d: asprintf failed: %s\n",
+		       __func__, __LINE__, strerror(errno));
+		return NULL;
+	}
+
+	/* Create and open temp file */
+	fd = mkstemps(tmpfile, strlen(".o"));
+	if (fd < 0) {
+		printf("%s@%d: mkstemps(%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"
+
+/*
+ * Test loading BPF program from an object file.
+ * This test uses same arguments as previous test_call1 example.
+ */
+static int
+test_bpf_elf_load(void)
+{
+	static const char test_section[] = "call1";
+	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(app_test_bpf_load_o,
+					     app_test_bpf_load_o_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, test_section);
+	unlink(tmpfile);
+	free(tmpfile);
+
+	/* If libelf support is not available */
+	if (bpf == NULL && rte_errno == ENOTSUP)
+		return TEST_SKIPPED;
+
+	TEST_ASSERT(bpf != NULL, "failed to load BPF %d:%s", 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 rc = rte_bpf_exec(bpf, tbuf);
+	ret = test_call1_check(rc, tbuf);
+	TEST_ASSERT(ret == 0, "test_call1_check failed: %d", ret);
+
+	/* 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;
+
+		rc = jit.func(tbuf);
+		ret = test_call1_check(rc, tbuf);
+		TEST_ASSERT(ret == 0, "jit test_call1_check failed: %d", ret);
+	}
+
+	rte_bpf_destroy(bpf);
+
+	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
Quoted 2 lines

static int
--

Quoted 1 line

2.51.0

#57 lore raw

PATCH v6 2/2 bpf: add test for Rx and Tx filtering

In reply to Stephen Hemminger

New test using null device to test filtering with BPF.

If libelf library is not available, then DPDK bpf will return -ENOTSUP to the test and the test will be skipped.

---
 app/test/bpf/filter.c    |  53 +++++++
 app/test/bpf/meson.build |   1 +
 app/test/test_bpf.c      | 321 ++++++++++++++++++++++++++++++++++++++-
 3 files changed, 373 insertions(+), 2 deletions(-)
 create mode 100644 app/test/bpf/filter.c
app/test/bpf/filter.c new file +53 -0
new file mode 100644
index 0000000000..d47233a47a
@@ -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;
+}
app/test/bpf/meson.build +1 -0
index b4f54aa976..19fec05521 100644
@@ -32,6 +32,7 @@ cflags += '-DTEST_BPF_ELF_LOAD'
 # BPF sources to compile
 bpf_progs = {
     'load' : 'test_bpf_load',
+    'filter' : 'test_bpf_filter',
 }
 
 foreach bpf_src, bpf_hdr : bpf_progs
app/test/test_bpf.c +319 -2

336 lines not on this page · raw message

Signature · 1 line
2.51.0
Acked-by on v6 2/2
#58 lore raw

RE: [PATCH v6 2/2] bpf: add test for Rx and Tx filtering

Quoted 444 lines

New test using null device to test filtering with BPF.

If libelf library is not available, then DPDK bpf will return -ENOTSUP to the test and the test will be skipped.

---
 app/test/bpf/filter.c    |  53 +++++++
 app/test/bpf/meson.build |   1 +
 app/test/test_bpf.c      | 321 ++++++++++++++++++++++++++++++++++++++-
 3 files changed, 373 insertions(+), 2 deletions(-)
 create mode 100644 app/test/bpf/filter.c
app/test/bpf/filter.c new file +53 -0
new file mode 100644
index 0000000000..d47233a47a
@@ -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;
+}
app/test/bpf/meson.build +1 -0
index b4f54aa976..19fec05521 100644
@@ -32,6 +32,7 @@ cflags += '-DTEST_BPF_ELF_LOAD'
 # BPF sources to compile
 bpf_progs = {
     'load' : 'test_bpf_load',
+    'filter' : 'test_bpf_filter',
 }
 
 foreach bpf_src, bpf_hdr : bpf_progs
app/test/test_bpf.c +84 -0
index c460002358..6bbcbb5eb3 100644
@@ -3424,10 +3424,326 @@ test_bpf_elf_load(void)
 	printf("%s: ELF load test passed\n", __func__);
 	return TEST_SUCCESS;
 }
+
+#include <rte_ethdev.h>
+#include <rte_bpf_ethdev.h>
+#include <rte_bus_vdev.h>
+
+#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

*,

app/test/test_bpf.c +5 -0
+							  sizeof(eh) + sizeof(iph));
+
+			ip->next_proto_id = IPPROTO_TCP;
+			*tcp = (struct rte_tcp_hdr) {
+				.src_port = rte_cpu_to_be_16(9),	/* discard

*/

app/test/test_bpf.c +1 -0
+				.dst_port = rte_cpu_to_be_16(9),	/* discard

*/

app/test/test_bpf.c +24 -0
+				.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 *section, uint32_t flags)
+{
+	const struct rte_bpf_prm prm = {
+		.prog_arg = {
+			.type = RTE_BPF_ARG_PTR,
+			.size = sizeof(struct rte_mbuf),
+		},
+	};
+	int ret;
+
+	/* Try to load BPF TX program from temp file */
+	ret = rte_bpf_eth_tx_elf_load(port, 0, &prm, tmpfile, section, flags);
+	if (ret != 0) {
+		printf("%s@%d: failed to load BPF filter from file=%s

error=%d:(%s)\n",

app/test/test_bpf.c +1 -0
+		       __func__, __LINE__, tmpfile, rte_errno,

rte_strerror(rte_errno));

app/test/test_bpf.c +11 -0
+		return 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);
+
+	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",

app/test/test_bpf.c +9 -0
+			  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);

app/test/test_bpf.c +19 -0
+
+	rte_bpf_eth_tx_unload(port, 0);
+	return TEST_SUCCESS;
+}
+
+/* Test loading a transmit filter which only allows IPv4 packets */
+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,

app/test/test_bpf.c +9 -0
+					  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(app_test_bpf_filter_o,

app_test_bpf_filter_o_len, "tx");

app/test/test_bpf.c +49 -0
+	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);
+
+	if (ret == 0)
+		return TEST_SUCCESS;
+	else if (ret == -ENOTSUP)
+		return TEST_SKIPPED;
+	else
+		return TEST_FAILED;
+}
+
+/* Test loading a receive filter */
+static int bpf_rx_test(uint16_t port, const char *tmpfile, struct rte_mempool *pool,
+		       const char *section, 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, section, flags);
+	if (ret != 0) {
+		printf("%s@%d: failed to load BPF filter from file=%s

error=%d:(%s)\n",

app/test/test_bpf.c +1 -0
+		       __func__, __LINE__, tmpfile, rte_errno,

rte_strerror(rte_errno));

app/test/test_bpf.c +6 -0
+		return 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);

app/test/test_bpf.c +10 -0
+
+	/* 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);

app/test/test_bpf.c +28 -0
+
+	return TEST_SUCCESS;
+}
+
+/* Test loading a receive filters, first with drop all and then with allow all packets */
+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(app_test_bpf_filter_o,

app_test_bpf_filter_o_len, "rx");

app/test/test_bpf.c +20 -0
+	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);

app/test/test_bpf.c +9 -0
+	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);

app/test/test_bpf.c +31 -1
+
+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;
+}
+
+
+static int
+test_bpf_elf(void)
+{
+	int ret;
+
+	ret = test_bpf_elf_load();
+	if (ret == TEST_SUCCESS)
+		ret = test_bpf_elf_tx_load();
+	if (ret == TEST_SUCCESS)
+		ret = test_bpf_elf_rx_load();
+
+	return ret;
+}
+
 #else
 
 static int
-test_bpf_elf_load(void)
+test_bpf_elf(void)
 {
 	printf("BPF compile not supported, skipping test\n");
 	return TEST_SKIPPED;

app/test/test_bpf.c test_bpf_elf_load(void)

Quoted 10 lines
app/test/test_bpf.c +2 -2
@@ -3435,7 +3751,8 @@ test_bpf_elf_load(void)
 
 #endif /* !TEST_BPF_ELF_LOAD */
 
-REGISTER_FAST_TEST(bpf_elf_load_autotest, true, true, test_bpf_elf_load);
+
+REGISTER_FAST_TEST(bpf_elf_autotest, true, true, test_bpf_elf);
 
 #ifndef RTE_HAS_LIBPCAP
 
--
Quoted 1 line

2.51.0

#59 lore raw

RE: [PATCH v6 0/2] BPF tests

In reply to Stephen Hemminger

With this version the new test succeeds or gets correctly skipped for me for all combinations of installed packages.

Quoted 31 lines

-----Original Message-----
From: Stephen Hemminger <[email protected]>
Sent: Tuesday 11 November 2025 22:56
To: [email protected]
Cc: Stephen Hemminger <[email protected]>
Subject: [PATCH v6 0/2] BPF tests

Add missing tests for BPF file loading functions. This is subset of earlier patch.

v6 - rebase to main
- make sure that -ENOTSUP is handled
- remove duplicate includes
- combine all of the elf tests into one test instance

Stephen Hemminger (2):
bpf: add a test for BPF ELF load
bpf: add test for Rx and Tx filtering

 app/test/bpf/filter.c    |  53 +++++
 app/test/bpf/load.c      |  51 +++++
 app/test/bpf/meson.build |  53 +++++
 app/test/meson.build     |   2 +
 app/test/test_bpf.c      | 476 +++++++++++++++++++++++++++++++++++++++
 5 files changed, 635 insertions(+)
 create mode 100644 app/test/bpf/filter.c
 create mode 100644 app/test/bpf/load.c
 create mode 100644 app/test/bpf/meson.build

--
2.51.0

#60 lore raw

Re: [PATCH v6 0/2] BPF tests

In reply to Stephen Hemminger

11/11/2025 23:55, Stephen Hemminger:

Quoted 11 lines

Add missing tests for BPF file loading functions.
This is subset of earlier patch.

v6 - rebase to main
- make sure that -ENOTSUP is handled
- remove duplicate includes
- combine all of the elf tests into one test instance

Stephen Hemminger (2):
bpf: add a test for BPF ELF load
bpf: add test for Rx and Tx filtering

Applied, thanks.