Linux RDMA and InfiniBand development
 help / color / mirror / Atom feed
* [PATCH v3 0/2] selftests/rdma: Add an eBPF-based testcase using netkit for RXE
@ 2026-10-21 20:31 Zhu Yanjun
  2026-10-21 20:31 ` [PATCH v3 1/2] selftests/rdma: Introduce eBPF kernel and user dump utilities " Zhu Yanjun
  2026-10-21 20:31 ` [PATCH v3 2/2] selftests/rdma: Add netkit-based automated test script " Zhu Yanjun
  0 siblings, 2 replies; 5+ messages in thread
From: Zhu Yanjun @ 2026-10-21 20:31 UTC (permalink / raw)
  To: jgg, leon, shuah, zyjzyj2000, linux-rdma, linux-kselftest,
	yanjun.zhu

Add a new self-test case to verify RXE (Soft-RoCE) traffic using
netkit virtual interfaces and an eBPF tcx ingress program to dump
and parse RDMA transport headers (BTH, etc.).

The test results:

# make -C tools/testing/selftests/ TARGETS=rdma run_tests
make: Entering directory '/root/linux/tools/testing/selftests'
clang -g -O2 -target bpf -D__TARGET_ARCH_x86 -I../../../../usr/include -I../../../../include/uapi -I../bpf -I/root/linux/tools/testing/selftests/rdma -c rxe_pkg_kernel_dump.c -o /root/linux/tools/testing/selftests/rdma/rxe_pkg_kernel_dump.o
bpftool gen skeleton /root/linux/tools/testing/selftests/rdma/rxe_pkg_kernel_dump.o > /root/linux/tools/testing/selftests/rdma/rxe_pkg_kernel.skel.h
gcc -D_GNU_SOURCE= -I/root/linux/tools/testing/selftests/../../../tools/testing/selftests  -g -Wall -I../../../../usr/include -I../../../../include/uapi -I../bpf -I/root/linux/tools/testing/selftests/rdma rxe_pkg_user_dump.c  -lbpf -lelf -lz -o /root/linux/tools/testing/selftests/rdma/rxe_netkit_dump
# timeout set to 45
# selftests: rdma: rxe_rping_between_netns.sh
# client DISCONNECT EVENT...
ok 1 selftests: rdma: rxe_rping_between_netns.sh
# timeout set to 45
# selftests: rdma: rxe_ipv6.sh
ok 2 selftests: rdma: rxe_ipv6.sh
# timeout set to 45
# selftests: rdma: rxe_socket_with_netns.sh
ok 3 selftests: rdma: rxe_socket_with_netns.sh
# timeout set to 45
# selftests: rdma: rxe_test_NETDEV_UNREGISTER.sh
ok 4 selftests: rdma: rxe_test_NETDEV_UNREGISTER.sh
# timeout set to 45
# selftests: rdma: rxe_sent_rcvd_bytes.sh
# server DISCONNECT EVENT...
# wait for RDMA_READ_ADV state 10
# client DISCONNECT EVENT...
ok 5 selftests: rdma: rxe_sent_rcvd_bytes.sh
# timeout set to 45
# selftests: rdma: rxe_netns_names.sh
# 1..0 # SKIP rdma netns exclusive mode is not supported
ok 6 selftests: rdma: rxe_netns_names.sh # SKIP
# timeout set to 45
# selftests: rdma: rxe_pkg_netkit.sh
# server DISCONNECT EVENT...
# wait for RDMA_READ_ADV state 10
# ./rxe_pkg_netkit.sh: line 28:  9866 Killed                  "$CUR_WD/rxe_netkit_dump"
ok 7 selftests: rdma: rxe_pkg_netkit.sh
make: Leaving directory '/root/linux/tools/testing/selftests'

---
V2 - > V3: Fix errs in user space application
V1 - > V2: Fix problems from sashiko
---

Zhu Yanjun (2):
  selftests/rdma: Introduce eBPF kernel and user dump utilities for RXE
  selftests/rdma: Add netkit-based automated test script for RXE

 tools/testing/selftests/rdma/Makefile         |   23 +-
 tools/testing/selftests/rdma/config           |    3 +
 .../selftests/rdma/rxe_pkg_kernel_dump.c      |   62 +
 .../testing/selftests/rdma/rxe_pkg_netkit.sh  |   63 +
 .../selftests/rdma/rxe_pkg_user_dump.c        | 1275 +++++++++++++++++
 5 files changed, 1425 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/rdma/rxe_pkg_kernel_dump.c
 create mode 100755 tools/testing/selftests/rdma/rxe_pkg_netkit.sh
 create mode 100644 tools/testing/selftests/rdma/rxe_pkg_user_dump.c

-- 
2.54.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v3 1/2] selftests/rdma: Introduce eBPF kernel and user dump utilities for RXE
  2026-10-21 20:31 [PATCH v3 0/2] selftests/rdma: Add an eBPF-based testcase using netkit for RXE Zhu Yanjun
@ 2026-10-21 20:31 ` Zhu Yanjun
  2026-10-21 20:46   ` sashiko-bot
  2026-10-21 20:31 ` [PATCH v3 2/2] selftests/rdma: Add netkit-based automated test script " Zhu Yanjun
  1 sibling, 1 reply; 5+ messages in thread
From: Zhu Yanjun @ 2026-10-21 20:31 UTC (permalink / raw)
  To: jgg, leon, shuah, zyjzyj2000, linux-rdma, linux-kselftest,
	yanjun.zhu

Add eBPF-based kernel and user-space packet dump utilities along with a
netkit test script (rxe_pkg_netkit.sh) to tools/testing/selftests/rdma/.
This enables capturing and parsing Soft-RoCE (RXE) traffic over netkit
devices for testing purposes.

Signed-off-by: Zhu Yanjun <yanjun.zhu@linux.dev>
---
 tools/testing/selftests/rdma/Makefile         |   24 +-
 tools/testing/selftests/rdma/config           |    3 +
 tools/testing/selftests/rdma/rxe_pkg_common.h |   17 +
 .../selftests/rdma/rxe_pkg_kernel_dump.c      |   77 +
 .../selftests/rdma/rxe_pkg_user_dump.c        | 1342 +++++++++++++++++
 5 files changed, 1462 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/rdma/rxe_pkg_common.h
 create mode 100644 tools/testing/selftests/rdma/rxe_pkg_kernel_dump.c
 create mode 100644 tools/testing/selftests/rdma/rxe_pkg_user_dump.c

diff --git a/tools/testing/selftests/rdma/Makefile b/tools/testing/selftests/rdma/Makefile
index a91c14c45006..ba225e580787 100644
--- a/tools/testing/selftests/rdma/Makefile
+++ b/tools/testing/selftests/rdma/Makefile
@@ -4,6 +4,28 @@ TEST_PROGS := rxe_rping_between_netns.sh \
 		rxe_socket_with_netns.sh \
 		rxe_test_NETDEV_UNREGISTER.sh \
 		rxe_sent_rcvd_bytes.sh \
-		rxe_netns_names.sh
+		rxe_netns_names.sh \
+		rxe_pkg_netkit.sh
 
+TEST_GEN_PROGS_EXTENDED := rxe_netkit_dump
 include ../lib.mk
+
+CLANG	?= clang
+LLC	?= llc
+BPFTOOL	?= bpftool
+ARCH ?= $(shell uname -m | sed 's/x86_64/x86/' | sed 's/aarch64/arm64/' | sed 's/ppc64le/powerpc/')
+
+INCLUDES	:= -I../../../../usr/include -I../../../../include/uapi -I../bpf -I$(OUTPUT)
+CFLAGS		+= -g -Wall $(INCLUDES)
+LDFLAGS		+= -lbpf -lelf -lz
+
+$(OUTPUT)/rxe_netkit_dump: rxe_pkg_user_dump.c $(OUTPUT)/rxe_pkg_kernel.skel.h
+	$(CC) $(CFLAGS) $< $(LDFLAGS) -o $@
+
+$(OUTPUT)/rxe_pkg_kernel_dump.o: rxe_pkg_kernel_dump.c
+	$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(ARCH) $(INCLUDES) -c $< -o $@
+
+$(OUTPUT)/rxe_pkg_kernel.skel.h: $(OUTPUT)/rxe_pkg_kernel_dump.o
+	$(BPFTOOL) gen skeleton $< > $@
+
+EXTRA_CLEAN := $(OUTPUT)/rxe_pkg_kernel_dump.o $(OUTPUT)/rxe_pkg_kernel.skel.h
diff --git a/tools/testing/selftests/rdma/config b/tools/testing/selftests/rdma/config
index e1ff54ec0f57..a98ffa95b0b7 100644
--- a/tools/testing/selftests/rdma/config
+++ b/tools/testing/selftests/rdma/config
@@ -3,3 +3,6 @@ CONFIG_VETH
 CONFIG_DUMMY
 CONFIG_NET_NS
 CONFIG_RDMA_RXE
+CONFIG_BPF=y
+CONFIG_BPF_SYSCALL=y
+CONFIG_NETKIT=y
diff --git a/tools/testing/selftests/rdma/rxe_pkg_common.h b/tools/testing/selftests/rdma/rxe_pkg_common.h
new file mode 100644
index 000000000000..0dca4147d5f9
--- /dev/null
+++ b/tools/testing/selftests/rdma/rxe_pkg_common.h
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
+/*
+ * Copyright (c) 2026 Zhu Yanjun <yanjun.zhu@linux.dev>
+ */
+
+// rxe_pkg_common.h
+#ifndef RXE_PKG_COMMON_H
+#define RXE_PKG_COMMON_H
+
+#define MAX_PKT_LEN 512
+
+struct pkg_event {
+	__u32 len;
+	__u8 data[MAX_PKT_LEN];
+};
+
+#endif
diff --git a/tools/testing/selftests/rdma/rxe_pkg_kernel_dump.c b/tools/testing/selftests/rdma/rxe_pkg_kernel_dump.c
new file mode 100644
index 000000000000..f465b52e7366
--- /dev/null
+++ b/tools/testing/selftests/rdma/rxe_pkg_kernel_dump.c
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
+/*
+ * Copyright (c) 2026 Zhu Yanjun <yanjun.zhu@linux.dev>
+ */
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <linux/if_ether.h>
+#include <linux/ip.h>
+#include <arpa/inet.h>
+
+#include "rxe_pkg_common.h"
+
+struct {
+	__uint(type, BPF_MAP_TYPE_RINGBUF);
+	__uint(max_entries, 256 * 1024);/* Shared by all CPUs */
+} rb SEC(".maps");
+
+SEC("netkit/primary")
+int handle_netkit_ingress(struct __sk_buff *skb)
+{
+	/* Get the header pointer of skb */
+	void *data_end = (void *)(long)skb->data_end;
+	void *data = (void *)(long)skb->data;
+
+	struct ethhdr *eth = data;
+
+	/* Verify the data */
+	if (data + sizeof(*eth) > data_end)
+		return BPF_OK;
+
+	/* Check if it is IP skb */
+	if (eth->h_proto == htons(ETH_P_IP)) {
+		struct iphdr *iph = data + sizeof(*eth);
+
+		if ((void *)iph + sizeof(*iph) <= data_end) {
+			bpf_printk("Got IP packet from netkit ingress, %d\n", skb->len);
+
+			__u32 len = skb->len;
+
+			/* It is a rping test, the size of packets should be
+			 * less than MAX_PKT_LEN
+			 */
+			if (len == 0 || len > MAX_PKT_LEN) {
+				bpf_printk("len is not in 0..MAX_PKT_LEN\n");
+				return BPF_OK;
+			}
+
+			struct pkg_event *e;
+
+			/* The len is fixed to MAX_PKT_LEN */
+			e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0);
+
+			if (!e) {
+				bpf_printk("bpf_ringbuf_reserve fails\n");
+				return BPF_OK;
+			}
+
+			e->len = len;
+
+			if (bpf_skb_load_bytes(skb, 0, e->data, len) < 0) {
+				bpf_ringbuf_discard(e, 0);
+				bpf_printk("bpf_skb_load_bytes fails\n");
+				return BPF_OK;
+			}
+
+			bpf_ringbuf_submit(e, 0);
+		}
+	}
+
+	/* BPF_OK:	Pass
+	 * BPF_DROP:	Drop skb
+	 */
+	return BPF_OK;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/rdma/rxe_pkg_user_dump.c b/tools/testing/selftests/rdma/rxe_pkg_user_dump.c
new file mode 100644
index 000000000000..0fe6d00c198e
--- /dev/null
+++ b/tools/testing/selftests/rdma/rxe_pkg_user_dump.c
@@ -0,0 +1,1342 @@
+// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
+/*
+ * Copyright (c) 2026 Zhu Yanjun <yanjun.zhu@linux.dev>
+ */
+
+#include <stdio.h>
+#include <sys/resource.h>
+#include <net/if.h>
+#include "rxe_pkg_kernel.skel.h"
+
+#include <linux/ip.h>
+#include <linux/udp.h>
+#include <ctype.h>
+
+#include <net/ethernet.h>
+#include <arpa/inet.h>
+
+#include "rxe_pkg_common.h"
+
+typedef unsigned char __u8;
+typedef unsigned short __u16;
+typedef unsigned int __u32;
+typedef unsigned long long __u64;
+typedef __u8 u8;
+typedef __u16 u16;
+typedef __u32 u32;
+typedef __u64 u64;
+
+struct rxe_bth {
+	__u8		opcode;
+	__u8		flags;
+	__be16		pkey;
+	__be32		qpn;
+	__be32		apsn;
+};
+
+struct rxe_immdt {
+	__be32		imm;
+};
+
+struct rxe_reth {
+	__be64		va;
+	__be32		rkey;
+	__be32		len;
+};
+
+struct rxe_aeth {
+	__be32		smsn;
+};
+
+/******************************************************************************
+ * Atomic Ack Extended Transport Header
+ ******************************************************************************/
+struct rxe_atmack {
+	__be64		orig;
+};
+
+/******************************************************************************
+ * Invalidate Extended Transport Header
+ ******************************************************************************/
+struct rxe_ieth {
+	__be32		rkey;
+};
+
+/******************************************************************************
+ * Datagram Extended Transport Header
+ ******************************************************************************/
+struct rxe_deth {
+	__be32	qkey;
+	__be32	sqp;
+};
+
+#define IB_OPCODE(transport, op) \
+	IB_OPCODE_ ## transport ## _ ## op = \
+		IB_OPCODE_ ## transport + IB_OPCODE_ ## op
+
+enum {
+	/* transport types -- just used to define real constants */
+	IB_OPCODE_RC                                = 0x00,
+	IB_OPCODE_UC                                = 0x20,
+	IB_OPCODE_RD                                = 0x40,
+	IB_OPCODE_UD                                = 0x60,
+	/* per IBTA 1.3 vol 1 Table 38, A10.3.2 */
+	IB_OPCODE_CNP                               = 0x80,
+	/* Manufacturer specific */
+	IB_OPCODE_MSP                               = 0xe0,
+
+	/* operations -- just used to define real constants */
+	IB_OPCODE_SEND_FIRST                        = 0x00,
+	IB_OPCODE_SEND_MIDDLE                       = 0x01,
+	IB_OPCODE_SEND_LAST                         = 0x02,
+	IB_OPCODE_SEND_LAST_WITH_IMMEDIATE          = 0x03,
+	IB_OPCODE_SEND_ONLY                         = 0x04,
+	IB_OPCODE_SEND_ONLY_WITH_IMMEDIATE          = 0x05,
+	IB_OPCODE_RDMA_WRITE_FIRST                  = 0x06,
+	IB_OPCODE_RDMA_WRITE_MIDDLE                 = 0x07,
+	IB_OPCODE_RDMA_WRITE_LAST                   = 0x08,
+	IB_OPCODE_RDMA_WRITE_LAST_WITH_IMMEDIATE    = 0x09,
+	IB_OPCODE_RDMA_WRITE_ONLY                   = 0x0a,
+	IB_OPCODE_RDMA_WRITE_ONLY_WITH_IMMEDIATE    = 0x0b,
+	IB_OPCODE_RDMA_READ_REQUEST                 = 0x0c,
+	IB_OPCODE_RDMA_READ_RESPONSE_FIRST          = 0x0d,
+	IB_OPCODE_RDMA_READ_RESPONSE_MIDDLE         = 0x0e,
+	IB_OPCODE_RDMA_READ_RESPONSE_LAST           = 0x0f,
+	IB_OPCODE_RDMA_READ_RESPONSE_ONLY           = 0x10,
+	IB_OPCODE_ACKNOWLEDGE                       = 0x11,
+	IB_OPCODE_ATOMIC_ACKNOWLEDGE                = 0x12,
+	IB_OPCODE_COMPARE_SWAP                      = 0x13,
+	IB_OPCODE_FETCH_ADD                         = 0x14,
+	/* opcode 0x15 is reserved */
+	IB_OPCODE_SEND_LAST_WITH_INVALIDATE         = 0x16,
+	IB_OPCODE_SEND_ONLY_WITH_INVALIDATE         = 0x17,
+
+	/* real constants follow -- see comment about above IB_OPCODE()
+	 * macro for more details
+	 */
+
+	/* RC */
+	IB_OPCODE(RC, SEND_FIRST),
+	IB_OPCODE(RC, SEND_MIDDLE),
+	IB_OPCODE(RC, SEND_LAST),
+	IB_OPCODE(RC, SEND_LAST_WITH_IMMEDIATE),
+	IB_OPCODE(RC, SEND_ONLY),
+	IB_OPCODE(RC, SEND_ONLY_WITH_IMMEDIATE),
+	IB_OPCODE(RC, RDMA_WRITE_FIRST),
+	IB_OPCODE(RC, RDMA_WRITE_MIDDLE),
+	IB_OPCODE(RC, RDMA_WRITE_LAST),
+	IB_OPCODE(RC, RDMA_WRITE_LAST_WITH_IMMEDIATE),
+	IB_OPCODE(RC, RDMA_WRITE_ONLY),
+	IB_OPCODE(RC, RDMA_WRITE_ONLY_WITH_IMMEDIATE),
+	IB_OPCODE(RC, RDMA_READ_REQUEST),
+	IB_OPCODE(RC, RDMA_READ_RESPONSE_FIRST),
+	IB_OPCODE(RC, RDMA_READ_RESPONSE_MIDDLE),
+	IB_OPCODE(RC, RDMA_READ_RESPONSE_LAST),
+	IB_OPCODE(RC, RDMA_READ_RESPONSE_ONLY),
+	IB_OPCODE(RC, ACKNOWLEDGE),
+	IB_OPCODE(RC, ATOMIC_ACKNOWLEDGE),
+	IB_OPCODE(RC, COMPARE_SWAP),
+	IB_OPCODE(RC, FETCH_ADD),
+	IB_OPCODE(RC, SEND_LAST_WITH_INVALIDATE),
+	IB_OPCODE(RC, SEND_ONLY_WITH_INVALIDATE),
+
+	/* UC */
+	IB_OPCODE(UC, SEND_FIRST),
+	IB_OPCODE(UC, SEND_MIDDLE),
+	IB_OPCODE(UC, SEND_LAST),
+	IB_OPCODE(UC, SEND_LAST_WITH_IMMEDIATE),
+	IB_OPCODE(UC, SEND_ONLY),
+	IB_OPCODE(UC, SEND_ONLY_WITH_IMMEDIATE),
+	IB_OPCODE(UC, RDMA_WRITE_FIRST),
+	IB_OPCODE(UC, RDMA_WRITE_MIDDLE),
+	IB_OPCODE(UC, RDMA_WRITE_LAST),
+	IB_OPCODE(UC, RDMA_WRITE_LAST_WITH_IMMEDIATE),
+	IB_OPCODE(UC, RDMA_WRITE_ONLY),
+	IB_OPCODE(UC, RDMA_WRITE_ONLY_WITH_IMMEDIATE),
+
+	/* RD */
+	IB_OPCODE(RD, SEND_FIRST),
+	IB_OPCODE(RD, SEND_MIDDLE),
+	IB_OPCODE(RD, SEND_LAST),
+	IB_OPCODE(RD, SEND_LAST_WITH_IMMEDIATE),
+	IB_OPCODE(RD, SEND_ONLY),
+	IB_OPCODE(RD, SEND_ONLY_WITH_IMMEDIATE),
+	IB_OPCODE(RD, RDMA_WRITE_FIRST),
+	IB_OPCODE(RD, RDMA_WRITE_MIDDLE),
+	IB_OPCODE(RD, RDMA_WRITE_LAST),
+	IB_OPCODE(RD, RDMA_WRITE_LAST_WITH_IMMEDIATE),
+	IB_OPCODE(RD, RDMA_WRITE_ONLY),
+	IB_OPCODE(RD, RDMA_WRITE_ONLY_WITH_IMMEDIATE),
+	IB_OPCODE(RD, RDMA_READ_REQUEST),
+	IB_OPCODE(RD, RDMA_READ_RESPONSE_FIRST),
+	IB_OPCODE(RD, RDMA_READ_RESPONSE_MIDDLE),
+	IB_OPCODE(RD, RDMA_READ_RESPONSE_LAST),
+	IB_OPCODE(RD, RDMA_READ_RESPONSE_ONLY),
+	IB_OPCODE(RD, ACKNOWLEDGE),
+	IB_OPCODE(RD, ATOMIC_ACKNOWLEDGE),
+	IB_OPCODE(RD, COMPARE_SWAP),
+	IB_OPCODE(RD, FETCH_ADD),
+
+	/* UD */
+	IB_OPCODE(UD, SEND_ONLY),
+	IB_OPCODE(UD, SEND_ONLY_WITH_IMMEDIATE)
+};
+
+enum rxe_hdr_type {
+	RXE_LRH,
+	RXE_GRH,
+	RXE_BTH,
+	RXE_RETH,
+	RXE_AETH,
+	RXE_ATMETH,
+	RXE_ATMACK,
+	RXE_IETH,
+	RXE_RDETH,
+	RXE_DETH,
+	RXE_IMMDT,
+	RXE_PAYLOAD,
+	NUM_HDR_TYPES
+};
+
+#define RXE_NUM_OPCODE		256
+
+struct rxe_opcode_info {
+	char			*name;
+	int			length;
+	int			offset[NUM_HDR_TYPES];
+};
+
+/******************************************************************************
+ * Atomic Extended Transport Header
+ ******************************************************************************/
+struct rxe_atmeth {
+	__be64			va;
+	__be32			rkey;
+	__be64			swap_add;
+	__be64			comp;
+} __attribute__((packed));
+
+/******************************************************************************
+ * Reliable Datagram Extended Transport Header
+ ******************************************************************************/
+struct rxe_rdeth {
+	__be32			een;
+};
+
+enum rxe_hdr_length {
+	RXE_BTH_BYTES		= sizeof(struct rxe_bth),
+	RXE_DETH_BYTES		= sizeof(struct rxe_deth),
+	RXE_IMMDT_BYTES		= sizeof(struct rxe_immdt),
+	RXE_RETH_BYTES		= sizeof(struct rxe_reth),
+	RXE_AETH_BYTES		= sizeof(struct rxe_aeth),
+	RXE_ATMACK_BYTES	= sizeof(struct rxe_atmack),
+	RXE_ATMETH_BYTES	= sizeof(struct rxe_atmeth),
+	RXE_IETH_BYTES		= sizeof(struct rxe_ieth),
+	RXE_RDETH_BYTES		= sizeof(struct rxe_rdeth),
+};
+
+struct rxe_opcode_info rxe_opcode[RXE_NUM_OPCODE] = {
+	[IB_OPCODE_RC_SEND_FIRST]			= {
+		.name	= "IB_OPCODE_RC_SEND_FIRST",
+		.length = RXE_BTH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES,
+		}
+	},
+	[IB_OPCODE_RC_SEND_MIDDLE]		= {
+		.name	= "IB_OPCODE_RC_SEND_MIDDLE",
+		.length = RXE_BTH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES,
+		}
+	},
+	[IB_OPCODE_RC_SEND_LAST]			= {
+		.name	= "IB_OPCODE_RC_SEND_LAST",
+		.length = RXE_BTH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES,
+		}
+	},
+	[IB_OPCODE_RC_SEND_LAST_WITH_IMMEDIATE]		= {
+		.name	= "IB_OPCODE_RC_SEND_LAST_WITH_IMMEDIATE",
+		.length = RXE_BTH_BYTES + RXE_IMMDT_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_IMMDT]	= RXE_BTH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_IMMDT_BYTES,
+		}
+	},
+	[IB_OPCODE_RC_SEND_ONLY]			= {
+		.name	= "IB_OPCODE_RC_SEND_ONLY",
+		.length = RXE_BTH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES,
+		}
+	},
+	[IB_OPCODE_RC_SEND_ONLY_WITH_IMMEDIATE]		= {
+		.name	= "IB_OPCODE_RC_SEND_ONLY_WITH_IMMEDIATE",
+		.length = RXE_BTH_BYTES + RXE_IMMDT_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_IMMDT]	= RXE_BTH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_IMMDT_BYTES,
+		}
+	},
+	[IB_OPCODE_RC_RDMA_WRITE_FIRST]		= {
+		.name	= "IB_OPCODE_RC_RDMA_WRITE_FIRST",
+		.length = RXE_BTH_BYTES + RXE_RETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RETH]	= RXE_BTH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_RETH_BYTES,
+		}
+	},
+	[IB_OPCODE_RC_RDMA_WRITE_MIDDLE]		= {
+		.name	= "IB_OPCODE_RC_RDMA_WRITE_MIDDLE",
+		.length = RXE_BTH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES,
+		}
+	},
+	[IB_OPCODE_RC_RDMA_WRITE_LAST]			= {
+		.name	= "IB_OPCODE_RC_RDMA_WRITE_LAST",
+		.length = RXE_BTH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES,
+		}
+	},
+	[IB_OPCODE_RC_RDMA_WRITE_LAST_WITH_IMMEDIATE]		= {
+		.name	= "IB_OPCODE_RC_RDMA_WRITE_LAST_WITH_IMMEDIATE",
+		.length = RXE_BTH_BYTES + RXE_IMMDT_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_IMMDT]	= RXE_BTH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_IMMDT_BYTES,
+		}
+	},
+	[IB_OPCODE_RC_RDMA_WRITE_ONLY]			= {
+		.name	= "IB_OPCODE_RC_RDMA_WRITE_ONLY",
+		.length = RXE_BTH_BYTES + RXE_RETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RETH]	= RXE_BTH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_RETH_BYTES,
+		}
+	},
+	[IB_OPCODE_RC_RDMA_WRITE_ONLY_WITH_IMMEDIATE]		= {
+		.name	= "IB_OPCODE_RC_RDMA_WRITE_ONLY_WITH_IMMEDIATE",
+		.length = RXE_BTH_BYTES + RXE_IMMDT_BYTES + RXE_RETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RETH]	= RXE_BTH_BYTES,
+			[RXE_IMMDT]	= RXE_BTH_BYTES +
+					  RXE_RETH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_RETH_BYTES +
+					  RXE_IMMDT_BYTES,
+		}
+	},
+	[IB_OPCODE_RC_RDMA_READ_REQUEST]			= {
+		.name	= "IB_OPCODE_RC_RDMA_READ_REQUEST",
+		.length = RXE_BTH_BYTES + RXE_RETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RETH]	= RXE_BTH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_RETH_BYTES,
+		}
+	},
+	[IB_OPCODE_RC_RDMA_READ_RESPONSE_FIRST]		= {
+		.name	= "IB_OPCODE_RC_RDMA_READ_RESPONSE_FIRST",
+		.length = RXE_BTH_BYTES + RXE_AETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_AETH]	= RXE_BTH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_AETH_BYTES,
+		}
+	},
+	[IB_OPCODE_RC_RDMA_READ_RESPONSE_MIDDLE]		= {
+		.name	= "IB_OPCODE_RC_RDMA_READ_RESPONSE_MIDDLE",
+		.length = RXE_BTH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES,
+		}
+	},
+	[IB_OPCODE_RC_RDMA_READ_RESPONSE_LAST]		= {
+		.name	= "IB_OPCODE_RC_RDMA_READ_RESPONSE_LAST",
+		.length = RXE_BTH_BYTES + RXE_AETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_AETH]	= RXE_BTH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_AETH_BYTES,
+		}
+	},
+	[IB_OPCODE_RC_RDMA_READ_RESPONSE_ONLY]		= {
+		.name	= "IB_OPCODE_RC_RDMA_READ_RESPONSE_ONLY",
+		.length = RXE_BTH_BYTES + RXE_AETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_AETH]	= RXE_BTH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_AETH_BYTES,
+		}
+	},
+	[IB_OPCODE_RC_ACKNOWLEDGE]			= {
+		.name	= "IB_OPCODE_RC_ACKNOWLEDGE",
+		.length = RXE_BTH_BYTES + RXE_AETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_AETH]	= RXE_BTH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_AETH_BYTES,
+		}
+	},
+	[IB_OPCODE_RC_ATOMIC_ACKNOWLEDGE]			= {
+		.name	= "IB_OPCODE_RC_ATOMIC_ACKNOWLEDGE",
+		.length = RXE_BTH_BYTES + RXE_ATMACK_BYTES + RXE_AETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_AETH]	= RXE_BTH_BYTES,
+			[RXE_ATMACK]	= RXE_BTH_BYTES +
+					  RXE_AETH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_ATMACK_BYTES +
+					  RXE_AETH_BYTES,
+		}
+	},
+	[IB_OPCODE_RC_COMPARE_SWAP]			= {
+		.name	= "IB_OPCODE_RC_COMPARE_SWAP",
+		.length = RXE_BTH_BYTES + RXE_ATMETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_ATMETH]	= RXE_BTH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_ATMETH_BYTES,
+		}
+	},
+	[IB_OPCODE_RC_FETCH_ADD]			= {
+		.name	= "IB_OPCODE_RC_FETCH_ADD",
+		.length = RXE_BTH_BYTES + RXE_ATMETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_ATMETH]	= RXE_BTH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_ATMETH_BYTES,
+		}
+	},
+	[IB_OPCODE_RC_SEND_LAST_WITH_INVALIDATE]		= {
+		.name	= "IB_OPCODE_RC_SEND_LAST_WITH_INVALIDATE",
+		.length = RXE_BTH_BYTES + RXE_IETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_IETH]	= RXE_BTH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_IETH_BYTES,
+		}
+	},
+	[IB_OPCODE_RC_SEND_ONLY_WITH_INVALIDATE]		= {
+		.name	= "IB_OPCODE_RC_SEND_ONLY_INV",
+		.length = RXE_BTH_BYTES + RXE_IETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_IETH]	= RXE_BTH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_IETH_BYTES,
+		}
+	},
+
+	/* UC */
+	[IB_OPCODE_UC_SEND_FIRST]			= {
+		.name	= "IB_OPCODE_UC_SEND_FIRST",
+		.length = RXE_BTH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES,
+		}
+	},
+	[IB_OPCODE_UC_SEND_MIDDLE]		= {
+		.name	= "IB_OPCODE_UC_SEND_MIDDLE",
+		.length = RXE_BTH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES,
+		}
+	},
+	[IB_OPCODE_UC_SEND_LAST]			= {
+		.name	= "IB_OPCODE_UC_SEND_LAST",
+		.length = RXE_BTH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES,
+		}
+	},
+	[IB_OPCODE_UC_SEND_LAST_WITH_IMMEDIATE]		= {
+		.name	= "IB_OPCODE_UC_SEND_LAST_WITH_IMMEDIATE",
+		.length = RXE_BTH_BYTES + RXE_IMMDT_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_IMMDT]	= RXE_BTH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_IMMDT_BYTES,
+		}
+	},
+	[IB_OPCODE_UC_SEND_ONLY]			= {
+		.name	= "IB_OPCODE_UC_SEND_ONLY",
+		.length = RXE_BTH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES,
+		}
+	},
+	[IB_OPCODE_UC_SEND_ONLY_WITH_IMMEDIATE]		= {
+		.name	= "IB_OPCODE_UC_SEND_ONLY_WITH_IMMEDIATE",
+		.length = RXE_BTH_BYTES + RXE_IMMDT_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_IMMDT]	= RXE_BTH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_IMMDT_BYTES,
+		}
+	},
+	[IB_OPCODE_UC_RDMA_WRITE_FIRST]		= {
+		.name	= "IB_OPCODE_UC_RDMA_WRITE_FIRST",
+		.length = RXE_BTH_BYTES + RXE_RETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RETH]	= RXE_BTH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_RETH_BYTES,
+		}
+	},
+	[IB_OPCODE_UC_RDMA_WRITE_MIDDLE]		= {
+		.name	= "IB_OPCODE_UC_RDMA_WRITE_MIDDLE",
+		.length = RXE_BTH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES,
+		}
+	},
+	[IB_OPCODE_UC_RDMA_WRITE_LAST]			= {
+		.name	= "IB_OPCODE_UC_RDMA_WRITE_LAST",
+		.length = RXE_BTH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES,
+		}
+	},
+	[IB_OPCODE_UC_RDMA_WRITE_LAST_WITH_IMMEDIATE]		= {
+		.name	= "IB_OPCODE_UC_RDMA_WRITE_LAST_WITH_IMMEDIATE",
+		.length = RXE_BTH_BYTES + RXE_IMMDT_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_IMMDT]	= RXE_BTH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_IMMDT_BYTES,
+		}
+	},
+	[IB_OPCODE_UC_RDMA_WRITE_ONLY]			= {
+		.name	= "IB_OPCODE_UC_RDMA_WRITE_ONLY",
+		.length = RXE_BTH_BYTES + RXE_RETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RETH]	= RXE_BTH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_RETH_BYTES,
+		}
+	},
+	[IB_OPCODE_UC_RDMA_WRITE_ONLY_WITH_IMMEDIATE]		= {
+		.name	= "IB_OPCODE_UC_RDMA_WRITE_ONLY_WITH_IMMEDIATE",
+		.length = RXE_BTH_BYTES + RXE_IMMDT_BYTES + RXE_RETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RETH]	= RXE_BTH_BYTES,
+			[RXE_IMMDT]	= RXE_BTH_BYTES +
+					  RXE_RETH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_RETH_BYTES +
+					  RXE_IMMDT_BYTES,
+		}
+	},
+
+	/* RD */
+	[IB_OPCODE_RD_SEND_FIRST]			= {
+		.name	= "IB_OPCODE_RD_SEND_FIRST",
+		.length = RXE_BTH_BYTES + RXE_DETH_BYTES + RXE_RDETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RDETH]	= RXE_BTH_BYTES,
+			[RXE_DETH]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES +
+					  RXE_DETH_BYTES,
+		}
+	},
+	[IB_OPCODE_RD_SEND_MIDDLE]		= {
+		.name	= "IB_OPCODE_RD_SEND_MIDDLE",
+		.length = RXE_BTH_BYTES + RXE_DETH_BYTES + RXE_RDETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RDETH]	= RXE_BTH_BYTES,
+			[RXE_DETH]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES +
+					  RXE_DETH_BYTES,
+		}
+	},
+	[IB_OPCODE_RD_SEND_LAST]			= {
+		.name	= "IB_OPCODE_RD_SEND_LAST",
+		.length = RXE_BTH_BYTES + RXE_DETH_BYTES + RXE_RDETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RDETH]	= RXE_BTH_BYTES,
+			[RXE_DETH]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES +
+					  RXE_DETH_BYTES,
+		}
+	},
+	[IB_OPCODE_RD_SEND_LAST_WITH_IMMEDIATE]		= {
+		.name	= "IB_OPCODE_RD_SEND_LAST_WITH_IMMEDIATE",
+		.length = RXE_BTH_BYTES + RXE_IMMDT_BYTES + RXE_DETH_BYTES +
+			  RXE_RDETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RDETH]	= RXE_BTH_BYTES,
+			[RXE_DETH]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES,
+			[RXE_IMMDT]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES +
+					  RXE_DETH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES +
+					  RXE_DETH_BYTES +
+					  RXE_IMMDT_BYTES,
+		}
+	},
+	[IB_OPCODE_RD_SEND_ONLY]			= {
+		.name	= "IB_OPCODE_RD_SEND_ONLY",
+		.length = RXE_BTH_BYTES + RXE_DETH_BYTES + RXE_RDETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RDETH]	= RXE_BTH_BYTES,
+			[RXE_DETH]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES +
+					  RXE_DETH_BYTES,
+		}
+	},
+	[IB_OPCODE_RD_SEND_ONLY_WITH_IMMEDIATE]		= {
+		.name	= "IB_OPCODE_RD_SEND_ONLY_WITH_IMMEDIATE",
+		.length = RXE_BTH_BYTES + RXE_IMMDT_BYTES + RXE_DETH_BYTES +
+			  RXE_RDETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RDETH]	= RXE_BTH_BYTES,
+			[RXE_DETH]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES,
+			[RXE_IMMDT]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES +
+					  RXE_DETH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES +
+					  RXE_DETH_BYTES +
+					  RXE_IMMDT_BYTES,
+		}
+	},
+	[IB_OPCODE_RD_RDMA_WRITE_FIRST]		= {
+		.name	= "IB_OPCODE_RD_RDMA_WRITE_FIRST",
+		.length = RXE_BTH_BYTES + RXE_RETH_BYTES + RXE_DETH_BYTES +
+			  RXE_RDETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RDETH]	= RXE_BTH_BYTES,
+			[RXE_DETH]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES,
+			[RXE_RETH]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES +
+					  RXE_DETH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES +
+					  RXE_DETH_BYTES +
+					  RXE_RETH_BYTES,
+		}
+	},
+	[IB_OPCODE_RD_RDMA_WRITE_MIDDLE]		= {
+		.name	= "IB_OPCODE_RD_RDMA_WRITE_MIDDLE",
+		.length = RXE_BTH_BYTES + RXE_DETH_BYTES + RXE_RDETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RDETH]	= RXE_BTH_BYTES,
+			[RXE_DETH]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES +
+					  RXE_DETH_BYTES,
+		}
+	},
+	[IB_OPCODE_RD_RDMA_WRITE_LAST]			= {
+		.name	= "IB_OPCODE_RD_RDMA_WRITE_LAST",
+		.length = RXE_BTH_BYTES + RXE_DETH_BYTES + RXE_RDETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RDETH]	= RXE_BTH_BYTES,
+			[RXE_DETH]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES +
+					  RXE_DETH_BYTES,
+		}
+	},
+	[IB_OPCODE_RD_RDMA_WRITE_LAST_WITH_IMMEDIATE]		= {
+		.name	= "IB_OPCODE_RD_RDMA_WRITE_LAST_WITH_IMMEDIATE",
+		.length = RXE_BTH_BYTES + RXE_IMMDT_BYTES + RXE_DETH_BYTES +
+			  RXE_RDETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RDETH]	= RXE_BTH_BYTES,
+			[RXE_DETH]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES,
+			[RXE_IMMDT]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES +
+					  RXE_DETH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES +
+					  RXE_DETH_BYTES +
+					  RXE_IMMDT_BYTES,
+		}
+	},
+	[IB_OPCODE_RD_RDMA_WRITE_ONLY]			= {
+		.name	= "IB_OPCODE_RD_RDMA_WRITE_ONLY",
+		.length = RXE_BTH_BYTES + RXE_RETH_BYTES + RXE_DETH_BYTES +
+			  RXE_RDETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RDETH]	= RXE_BTH_BYTES,
+			[RXE_DETH]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES,
+			[RXE_RETH]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES +
+					  RXE_DETH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES +
+					  RXE_DETH_BYTES +
+					  RXE_RETH_BYTES,
+		}
+	},
+	[IB_OPCODE_RD_RDMA_WRITE_ONLY_WITH_IMMEDIATE]		= {
+		.name	= "IB_OPCODE_RD_RDMA_WRITE_ONLY_WITH_IMMEDIATE",
+		.length = RXE_BTH_BYTES + RXE_IMMDT_BYTES + RXE_RETH_BYTES +
+			  RXE_DETH_BYTES + RXE_RDETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RDETH]	= RXE_BTH_BYTES,
+			[RXE_DETH]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES,
+			[RXE_RETH]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES +
+					  RXE_DETH_BYTES,
+			[RXE_IMMDT]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES +
+					  RXE_DETH_BYTES +
+					  RXE_RETH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES +
+					  RXE_DETH_BYTES +
+					  RXE_RETH_BYTES +
+					  RXE_IMMDT_BYTES,
+		}
+	},
+	[IB_OPCODE_RD_RDMA_READ_REQUEST]			= {
+		.name	= "IB_OPCODE_RD_RDMA_READ_REQUEST",
+		.length = RXE_BTH_BYTES + RXE_RETH_BYTES + RXE_DETH_BYTES +
+			  RXE_RDETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RDETH]	= RXE_BTH_BYTES,
+			[RXE_DETH]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES,
+			[RXE_RETH]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES +
+					  RXE_DETH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_RETH_BYTES +
+					  RXE_DETH_BYTES +
+					  RXE_RDETH_BYTES,
+		}
+	},
+	[IB_OPCODE_RD_RDMA_READ_RESPONSE_FIRST]		= {
+		.name	= "IB_OPCODE_RD_RDMA_READ_RESPONSE_FIRST",
+		.length = RXE_BTH_BYTES + RXE_AETH_BYTES + RXE_RDETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RDETH]	= RXE_BTH_BYTES,
+			[RXE_AETH]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES +
+					  RXE_AETH_BYTES,
+		}
+	},
+	[IB_OPCODE_RD_RDMA_READ_RESPONSE_MIDDLE]		= {
+		.name	= "IB_OPCODE_RD_RDMA_READ_RESPONSE_MIDDLE",
+		.length = RXE_BTH_BYTES + RXE_RDETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RDETH]	= RXE_BTH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES,
+		}
+	},
+	[IB_OPCODE_RD_RDMA_READ_RESPONSE_LAST]		= {
+		.name	= "IB_OPCODE_RD_RDMA_READ_RESPONSE_LAST",
+		.length = RXE_BTH_BYTES + RXE_AETH_BYTES + RXE_RDETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RDETH]	= RXE_BTH_BYTES,
+			[RXE_AETH]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES +
+					  RXE_AETH_BYTES,
+		}
+	},
+	[IB_OPCODE_RD_RDMA_READ_RESPONSE_ONLY]		= {
+		.name	= "IB_OPCODE_RD_RDMA_READ_RESPONSE_ONLY",
+		.length = RXE_BTH_BYTES + RXE_AETH_BYTES + RXE_RDETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RDETH]	= RXE_BTH_BYTES,
+			[RXE_AETH]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES +
+					  RXE_AETH_BYTES,
+		}
+	},
+	[IB_OPCODE_RD_ACKNOWLEDGE]			= {
+		.name	= "IB_OPCODE_RD_ACKNOWLEDGE",
+		.length = RXE_BTH_BYTES + RXE_AETH_BYTES + RXE_RDETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RDETH]	= RXE_BTH_BYTES,
+			[RXE_AETH]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES,
+		}
+	},
+	[IB_OPCODE_RD_ATOMIC_ACKNOWLEDGE]			= {
+		.name	= "IB_OPCODE_RD_ATOMIC_ACKNOWLEDGE",
+		.length = RXE_BTH_BYTES + RXE_ATMACK_BYTES + RXE_AETH_BYTES +
+			  RXE_RDETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RDETH]	= RXE_BTH_BYTES,
+			[RXE_AETH]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES,
+			[RXE_ATMACK]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES +
+					  RXE_AETH_BYTES,
+		}
+	},
+	[IB_OPCODE_RD_COMPARE_SWAP]			= {
+		.name	= "RD_COMPARE_SWAP",
+		.length = RXE_BTH_BYTES + RXE_ATMETH_BYTES + RXE_DETH_BYTES +
+			  RXE_RDETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RDETH]	= RXE_BTH_BYTES,
+			[RXE_DETH]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES,
+			[RXE_ATMETH]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES +
+					  RXE_DETH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_ATMETH_BYTES +
+					  RXE_DETH_BYTES +
+					  RXE_RDETH_BYTES,
+		}
+	},
+	[IB_OPCODE_RD_FETCH_ADD]			= {
+		.name	= "IB_OPCODE_RD_FETCH_ADD",
+		.length = RXE_BTH_BYTES + RXE_ATMETH_BYTES + RXE_DETH_BYTES +
+			  RXE_RDETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_RDETH]	= RXE_BTH_BYTES,
+			[RXE_DETH]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES,
+			[RXE_ATMETH]	= RXE_BTH_BYTES +
+					  RXE_RDETH_BYTES +
+					  RXE_DETH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_ATMETH_BYTES +
+					  RXE_DETH_BYTES +
+					  RXE_RDETH_BYTES,
+		}
+	},
+
+	/* UD */
+	[IB_OPCODE_UD_SEND_ONLY]			= {
+		.name	= "IB_OPCODE_UD_SEND_ONLY",
+		.length = RXE_BTH_BYTES + RXE_DETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_DETH]	= RXE_BTH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_DETH_BYTES,
+		}
+	},
+	[IB_OPCODE_UD_SEND_ONLY_WITH_IMMEDIATE]		= {
+		.name	= "IB_OPCODE_UD_SEND_ONLY_WITH_IMMEDIATE",
+		.length = RXE_BTH_BYTES + RXE_IMMDT_BYTES + RXE_DETH_BYTES,
+		.offset = {
+			[RXE_BTH]	= 0,
+			[RXE_DETH]	= RXE_BTH_BYTES,
+			[RXE_IMMDT]	= RXE_BTH_BYTES +
+					  RXE_DETH_BYTES,
+			[RXE_PAYLOAD]	= RXE_BTH_BYTES +
+					  RXE_DETH_BYTES +
+					  RXE_IMMDT_BYTES,
+		}
+	},
+
+};
+
+static inline u8 __bth_opcode(void *arg)
+{
+	struct rxe_bth *bth = arg;
+
+	return bth->opcode;
+}
+
+static inline u8 bth_opcode(struct rxe_bth *bth)
+{
+	return __bth_opcode(bth);
+}
+
+static inline u8 bth_flags(struct rxe_bth *bth)
+{
+	return bth->flags;
+}
+
+static inline u16 __bth_pkey(void *arg)
+{
+	struct rxe_bth *bth = arg;
+
+	return ntohs(bth->pkey);
+}
+
+static inline u16 bth_pkey(struct rxe_bth *bth)
+{
+	return __bth_pkey(bth);
+}
+
+#define BTH_QPN_MASK		(0x00ffffff)
+#define BTH_PSN_MASK		((1 << 24) - 1)
+
+static inline u32 __bth_qpn(void *arg)
+{
+	struct rxe_bth *bth = arg;
+
+	return BTH_QPN_MASK & ntohl(bth->qpn);
+}
+
+static inline u32 bth_qpn(struct rxe_bth *bth)
+{
+	return __bth_qpn(bth);
+}
+
+static inline u32 __bth_psn(void *arg)
+{
+	struct rxe_bth *bth = arg;
+
+	return BTH_PSN_MASK & ntohl(bth->apsn);
+}
+
+static inline u32 bth_psn(struct rxe_bth *bth)
+{
+	return __bth_psn(bth);
+}
+
+static inline void print_bth(struct rxe_bth *bth)
+{
+	printf("struct rxe_bth {\n");
+	printf("\t__u8            opcode; 0x%x\n", bth_opcode(bth));
+	printf("\t__u8            flags;  0x%x\n", bth_flags(bth));
+	printf("\t__be16          pkey;   0x%x\n", bth_pkey(bth));
+	printf("\t__be32          qpn;    0x%x\n", bth_qpn(bth));
+	printf("\t__be32          apsn;   0x%x\n", bth_psn(bth));
+	printf("};\n");
+}
+
+static inline u64 reth_va(struct rxe_reth *reth)
+{
+	return be64toh(reth->va);
+}
+
+static inline u32 reth_rkey(struct rxe_reth *reth)
+{
+	return ntohl(reth->rkey);
+}
+
+static inline u32 reth_len(struct rxe_reth *reth)
+{
+	return ntohl(reth->len);
+}
+
+static inline void print_reth(struct rxe_reth *reth)
+{
+	printf("struct rxe_reth {\n");
+	printf("\t__be64	va;	0x%llx\n", reth_va(reth));
+	printf("\t__be32	rkey;	0x%x\n", reth_rkey(reth));
+	printf("\t__be32	len;	0x%x\n", reth_len(reth));
+
+	printf("};\n");
+}
+
+#define AETH_MSN_MASK		(0x00ffffff)
+
+static inline u32 aeth_msn(struct rxe_aeth *aeth)
+{
+	return AETH_MSN_MASK & ntohl(aeth->smsn);
+}
+
+static inline void print_aeth(struct rxe_aeth *aeth)
+{
+	printf("struct rxe_aeth {\n");
+	printf("\t___be32		smsn; 0x%x\n", aeth_msn(aeth));
+	printf("};\n");
+}
+
+static inline u64 atmeth_va(struct rxe_atmeth *atmeth)
+{
+	return be64toh(atmeth->va);
+}
+
+static inline u32 atmeth_rkey(struct rxe_atmeth *atmeth)
+{
+	return ntohl(atmeth->rkey);
+}
+
+static inline u64 atmeth_swap_add(struct rxe_atmeth *atmeth)
+{
+	return be64toh(atmeth->swap_add);
+}
+
+static inline u64 atmeth_comp(struct rxe_atmeth *atmeth)
+{
+	return be64toh(atmeth->comp);
+}
+
+static inline void print_atmeth(struct rxe_atmeth *atmeth)
+{
+	printf("struct rxe_atmeth {\n");
+	printf("\t__be64		va;       0x%llx\n", atmeth_va(atmeth));
+	printf("\t__be32		rkey;     0x%x\n", atmeth_rkey(atmeth));
+	printf("\t__be64		swap_add; 0x%llx\n", atmeth_swap_add(atmeth));
+	printf("\t__be64		comp;     0x%llx\n", atmeth_comp(atmeth));
+	printf("} __packed;\n");
+}
+
+static inline u64 atmack_orig(struct rxe_atmack *atmack)
+{
+	return be64toh(atmack->orig);
+}
+
+static inline void print_atmack(struct rxe_atmack *atmack)
+{
+	printf("struct rxe_atmack {\n");
+	printf("\t__be64		orig; 0x%llx\n", atmack_orig(atmack));
+	printf("};\n");
+}
+
+static inline u32 ieth_rkey(struct rxe_ieth *ieth)
+{
+	return ntohl(ieth->rkey);
+}
+
+static inline void print_ieth(struct rxe_ieth *ieth)
+{
+	printf("struct rxe_ieth {\n");
+	printf("\t__be32		rkey; 0x%x\n", ieth_rkey(ieth));
+	printf("};\n");
+}
+
+#define RDETH_EEN_MASK		(0x00ffffff)
+
+static inline u32 rdeth_een(struct rxe_rdeth *rdeth)
+{
+	return RDETH_EEN_MASK & ntohl(rdeth->een);
+}
+
+static inline void print_rdeth(struct rxe_rdeth *rdeth)
+{
+	printf("struct rxe_rdeth {\n");
+	printf("\t__be32		een; 0x%x\n", rdeth_een(rdeth));
+	printf("};\n");
+}
+
+#define DETH_SQP_MASK		(0x00ffffff)
+
+static inline u32 deth_qkey(struct rxe_deth *deth)
+{
+	return ntohl(deth->qkey);
+}
+
+static inline u32 deth_sqp(struct rxe_deth *deth)
+{
+	return DETH_SQP_MASK & ntohl(deth->sqp);
+}
+
+static inline void print_deth(struct rxe_deth *deth)
+{
+	printf("struct rxe_deth {\n");
+	printf("\t__be32	qkey; 0x%x\n", deth_qkey(deth));
+	printf("\t__be32	sqp;  0x%x\n", deth_sqp(deth));
+	printf("};\n");
+}
+
+static inline __be32 immdt_imm(struct rxe_immdt *immdt)
+{
+	return ntohl(immdt->imm);
+}
+
+static inline void print_immdt(struct rxe_immdt *immdt)
+{
+	printf("struct rxe_immdt {\n");
+	printf("\t__be32		imm; 0x%x\n", immdt_imm(immdt));
+	printf("};\n");
+}
+
+static inline void print_ipaddr(struct iphdr *iph)
+{
+	unsigned char *ip_addr = (unsigned char *)&iph->saddr;
+
+	printf("struct iphdr {\n");
+	printf("\t__be32	saddr; %d.%d.%d.%d\n",
+			ip_addr[0], ip_addr[1], ip_addr[2], ip_addr[3]);
+	ip_addr = (unsigned char *)&iph->daddr;
+	printf("\t__be32	daddr; %d.%d.%d.%d\n",
+			ip_addr[0], ip_addr[1], ip_addr[2], ip_addr[3]);
+	printf("};\n");
+}
+
+static inline void print_udphdr(struct udphdr *udp_hdr)
+{
+	printf("struct udphdr {\n");
+	printf("\t__be16	source; %d\n", ntohs(udp_hdr->source));
+	printf("\t__be16	dest;   %d\n", ntohs(udp_hdr->dest));
+	printf("\t__be16	len;    %d\n", ntohs(udp_hdr->len));
+	printf("\t__sum16	check;  %d\n", ntohs(udp_hdr->check));
+	printf("};\n");
+}
+
+#define ROCEV2_PORT 4791
+
+static void print_bpf_output(const void *data, __u32 size)
+{
+	/* Validate size covers IP and UDP headers */
+	if (size < sizeof(struct iphdr) + sizeof(struct udphdr))
+		return;
+
+	struct iphdr *iph = (struct iphdr *)data;
+
+	if (iph->protocol != IPPROTO_UDP)
+		return;
+
+	struct udphdr *udp_hdr = (struct udphdr *)(data + sizeof(struct iphdr));
+
+	if (ntohs(udp_hdr->dest) != ROCEV2_PORT)
+		return;
+
+	print_ipaddr(iph);
+	printf("udp hdr:\n");
+	print_udphdr(udp_hdr);
+
+	size_t base_hdr_size = sizeof(struct iphdr) + sizeof(struct udphdr)
+				+ sizeof(struct rxe_bth);
+
+	if (size < base_hdr_size)
+		return;
+
+	struct rxe_bth *bth = (struct rxe_bth *)(data + sizeof(struct iphdr)
+				+ sizeof(struct udphdr));
+
+	/* Validate opcode bounds and ensure name exists to prevent NULL dereference */
+	if (bth->opcode >= 256 || !rxe_opcode[bth->opcode].name) {
+		printf("Unknown or unpopulated opcode: 0x%x\n", bth->opcode);
+		return;
+	}
+
+	printf("%s\n", rxe_opcode[bth->opcode].name);
+	print_bth(bth);
+
+	for (int i = RXE_BTH + 1; i < NUM_HDR_TYPES; i++) {
+		int offset = rxe_opcode[bth->opcode].offset[i];
+
+		if (offset && (size >= sizeof(struct iphdr) + sizeof(struct udphdr) + offset)) {
+			char *cur_p = (char *)bth + offset;
+
+			switch (i) {
+			case	RXE_RETH:
+				printf("RXE_RETH\n");
+				struct rxe_reth *reth = (struct rxe_reth *)cur_p;
+
+				print_reth(reth);
+				break;
+
+			case	RXE_AETH:
+				printf("RXE_AETH\n");
+				struct rxe_aeth *aeth = (struct rxe_aeth *)cur_p;
+
+				print_aeth(aeth);
+				break;
+
+			case	RXE_ATMETH:
+				printf("RXE_ATMETH\n");
+				struct rxe_atmeth *atmeth = (struct rxe_atmeth *)cur_p;
+
+				print_atmeth(atmeth);
+				break;
+
+			case	RXE_ATMACK:
+				printf("RXE_ATMACK\n");
+				struct rxe_atmack *atmack = (struct rxe_atmack *)cur_p;
+
+				print_atmack(atmack);
+				break;
+
+			case	RXE_IETH:
+				printf("RXE_IETH\n");
+				struct rxe_ieth *ieth = (struct rxe_ieth *)cur_p;
+
+				print_ieth(ieth);
+				break;
+
+			case	RXE_RDETH:
+				printf("RXE_RDETH\n");
+				struct rxe_rdeth *rdeth = (struct rxe_rdeth *)cur_p;
+
+				print_rdeth(rdeth);
+				break;
+
+			case	RXE_DETH:
+				printf("RXE_DETH\n");
+				struct rxe_deth *deth = (struct rxe_deth *)cur_p;
+
+				print_deth(deth);
+				break;
+
+			case	RXE_IMMDT:
+				printf("RXE_IMMDT\n");
+				struct rxe_immdt *immdt = (struct rxe_immdt *)cur_p;
+
+				print_immdt(immdt);
+				break;
+
+			case	RXE_PAYLOAD:
+				printf("RXE_PAYLOAD\n");
+				int len = ntohs(udp_hdr->len) - rxe_opcode[bth->opcode].length;
+
+				for (int j = 0; j < len; j++) {
+					if (isprint(cur_p[j]))
+						printf("%c", cur_p[j]);
+					else
+						printf("%02x", cur_p[j]);
+				}
+				printf("\n");
+				break;
+			default:
+				break;
+			}
+		}
+	}
+
+	printf("\n");
+}
+
+static int handle_event(void *ctx, void *data, unsigned long data_sz)
+{
+	const struct pkg_event *e = data;
+
+	if (data_sz < sizeof(*e))
+		return -1;
+
+	print_bpf_output(e->data + sizeof(struct ethhdr),
+				e->len - sizeof(struct ethhdr));
+
+	return 0;
+}
+
+int main(void)
+{
+	struct rxe_pkg_kernel_dump *skel;
+	int err = 1;
+	struct ring_buffer *ringbuf = NULL;
+	struct rlimit rlim = { .rlim_cur = RLIM_INFINITY, .rlim_max = RLIM_INFINITY };
+
+	if (setrlimit(RLIMIT_MEMLOCK, &rlim)) {
+		perror("setrlimit failed");
+		return 1;
+	}
+
+	skel = rxe_pkg_kernel_dump__open_and_load();
+	if (!skel) {
+		fprintf(stderr, "Failed to open and load BPF skeleton\n");
+		return 1;
+	}
+
+	int ifindex = if_nametoindex("nk0");
+
+	if (ifindex == 0) {
+		perror("if_nametoindex nk0 failed");
+		goto cleanup;
+	}
+
+	skel->links.handle_netkit_ingress = bpf_program__attach_netkit(
+		skel->progs.handle_netkit_ingress, ifindex, NULL);
+
+	if (!skel->links.handle_netkit_ingress) {
+		fprintf(stderr, "Failed to attach to netkit nk0\n");
+		goto cleanup;
+	}
+
+	ringbuf = ring_buffer__new(bpf_map__fd(skel->maps.rb),
+				   handle_event, NULL, NULL);
+	if (!ringbuf) {
+		fprintf(stderr, "Failed to create ring buffer\n");
+		goto cleanup;
+	}
+
+	err = 0;
+
+	while (1) {
+		err = ring_buffer__poll(ringbuf, 100 /* timeout */);
+		if (err < 0 && err != -EINTR) {
+			fprintf(stderr, "poll error\n");
+			break;
+		}
+	}
+
+cleanup:
+	ring_buffer__free(ringbuf);
+	rxe_pkg_kernel_dump__destroy(skel);
+	return err;
+}
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH v3 2/2] selftests/rdma: Add netkit-based automated test script for RXE
  2026-10-21 20:31 [PATCH v3 0/2] selftests/rdma: Add an eBPF-based testcase using netkit for RXE Zhu Yanjun
  2026-10-21 20:31 ` [PATCH v3 1/2] selftests/rdma: Introduce eBPF kernel and user dump utilities " Zhu Yanjun
@ 2026-10-21 20:31 ` Zhu Yanjun
  2026-10-21 20:46   ` sashiko-bot
  1 sibling, 1 reply; 5+ messages in thread
From: Zhu Yanjun @ 2026-10-21 20:31 UTC (permalink / raw)
  To: jgg, leon, shuah, zyjzyj2000, linux-rdma, linux-kselftest,
	yanjun.zhu

Add a shell script to automate functional and tracing tests for the
Soft-RoCE (RXE) driver over netkit network interfaces. The script creates
an isolated network namespace, sets up a netkit link pair (nk0/nk1),
attaches RXE links, and runs an rping client/server verification test
along with kernel tracing output collection.

Signed-off-by: Zhu Yanjun <yanjun.zhu@linux.dev>
---
 .../testing/selftests/rdma/rxe_pkg_netkit.sh  | 81 +++++++++++++++++++
 1 file changed, 81 insertions(+)
 create mode 100755 tools/testing/selftests/rdma/rxe_pkg_netkit.sh

diff --git a/tools/testing/selftests/rdma/rxe_pkg_netkit.sh b/tools/testing/selftests/rdma/rxe_pkg_netkit.sh
new file mode 100755
index 000000000000..181e2d85eed7
--- /dev/null
+++ b/tools/testing/selftests/rdma/rxe_pkg_netkit.sh
@@ -0,0 +1,81 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
+# Copyright (c) 2026 Zhu Yanjun <yanjun.zhu@linux.dev>
+if [ "$(id -u)" -ne 0 ]; then
+    echo "SKIP: This test must be run as root"
+    exit 4
+fi
+
+for cmd in ip rdma rping; do
+    if ! command -v "$cmd" >/dev/null 2>&1; then
+        echo "SKIP: Required tool '$cmd' not found"
+        exit 4
+    fi
+done
+
+if ! modprobe -n netkit >/dev/null 2>&1 || ! modprobe -n rdma_rxe >/dev/null 2>&1; then
+    echo "SKIP: Required kernel modules (netkit or rdma_rxe) are not available"
+    exit 4
+fi
+
+exec > /dev/null
+
+set -e
+
+CUR_WD="$(dirname "$0")"
+[ "$CUR_WD" = "." ] && CUR_WD="$(pwd)"
+
+cleanup() {
+    set +e
+    [ -n "$DUMP_PID" ] && kill -9 "$DUMP_PID" 2>/dev/null
+    unset DUMP_PID
+
+    rdma link del rxe1 2>/dev/null
+    ip link del nk0 2>/dev/null
+    ip netns delete ns0 2>/dev/null
+}
+
+trap cleanup EXIT INT TERM
+
+# Clear tracing buffer
+echo > /sys/kernel/debug/tracing/trace
+
+# Create network namespace and netkit peer links
+ip netns add ns0
+ip link add nk0 type netkit mode l2 peer name nk1
+ip link set nk0 up
+ip link set nk1 up
+
+# Configure namespace interface
+ip link set nk1 netns ns0
+ip netns exec ns0 ip addr add 10.0.0.1/24 dev nk1
+ip netns exec ns0 ip link set nk1 up
+
+# Configure host interface
+ip addr add 10.0.0.2/24 dev nk0
+
+# Start dump utility in background
+"$CUR_WD/rxe_netkit_dump" &
+DUMP_PID=$!
+
+# Add RXE links over netkit devices
+ip netns exec ns0 rdma link add rxe0 type rxe netdev nk1
+rdma link add rxe1 type rxe netdev nk0
+
+# Start rping server in namespace
+ip netns exec ns0 rping -s -a 10.0.0.1 -C 3 &
+
+# Add wait time
+sleep 1
+
+# Run rping client on host
+rping -c -a 10.0.0.1 -C 3
+
+# Tear down RDMA links and netkit devices
+ip netns exec ns0 rdma link del rxe0
+rdma link del rxe1
+ip link del nk0
+ip netns delete ns0
+
+# Display kernel trace output
+cat /sys/kernel/debug/tracing/trace
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH v3 1/2] selftests/rdma: Introduce eBPF kernel and user dump utilities for RXE
  2026-10-21 20:31 ` [PATCH v3 1/2] selftests/rdma: Introduce eBPF kernel and user dump utilities " Zhu Yanjun
@ 2026-10-21 20:46   ` sashiko-bot
  0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-10-21 20:46 UTC (permalink / raw)
  To: Zhu Yanjun, linux-rdma

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Medium] selftests/rdma: out-of-bounds read in print_bpf_output() packet parsing
- [Medium] selftests/rdma: invalid userspace header and build flags for BPF dump object
- [Medium] selftests/rdma: missing signal handling and stdout flush in main()
- [Low] selftests/rdma: conflicting __u64 typedef in rxe_pkg_user_dump.c
--

commit 812649f812ece426007a23045167b73c8aff9b63
Author: Zhu Yanjun <yanjun.zhu@linux.dev>

selftests/rdma: Introduce eBPF kernel and user dump utilities for RXE

This commit adds an eBPF program and a user-space ring-buffer consumer utility
to capture and dump Soft-RoCE (rxe) packets over netkit devices in the rdma
selftests.

> diff --git a/tools/testing/selftests/rdma/Makefile b/tools/testing/selftests/rdma/Makefile
> index a91c14c45006e..ba225e580787d 100644
> --- a/tools/testing/selftests/rdma/Makefile
> +++ b/tools/testing/selftests/rdma/Makefile
> @@ -4,6 +4,28 @@ TEST_PROGS := rxe_rping_between_netns.sh \
>  		rxe_socket_with_netns.sh \
>  		rxe_test_NETDEV_UNREGISTER.sh \
>  		rxe_sent_rcvd_bytes.sh \
> -		rxe_netns_names.sh
> +		rxe_netns_names.sh \
> +		rxe_pkg_netkit.sh
>  
> +TEST_GEN_PROGS_EXTENDED := rxe_netkit_dump
>  include ../lib.mk
> +
> +CLANG	?= clang
> +LLC	?= llc
> +BPFTOOL	?= bpftool
> +ARCH ?= $(shell uname -m | sed 's/x86_64/x86/' | sed 's/aarch64/arm64/' | sed 's/ppc64le/powerpc/')
> +
> +INCLUDES	:= -I../../../../usr/include -I../../../../include/uapi -I../bpf -I$(OUTPUT)
> +CFLAGS		+= -g -Wall $(INCLUDES)
> +LDFLAGS		+= -lbpf -lelf -lz
> +
> +$(OUTPUT)/rxe_netkit_dump: rxe_pkg_user_dump.c $(OUTPUT)/rxe_pkg_kernel.skel.h
> +	$(CC) $(CFLAGS) $< $(LDFLAGS) -o $@
> +
> +$(OUTPUT)/rxe_pkg_kernel_dump.o: rxe_pkg_kernel_dump.c
> +	$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(ARCH) $(INCLUDES) -c $< -o $@
> +
> +$(OUTPUT)/rxe_pkg_kernel.skel.h: $(OUTPUT)/rxe_pkg_kernel_dump.o
> +	$(BPFTOOL) gen skeleton $< > $@
> +
> +EXTRA_CLEAN := $(OUTPUT)/rxe_pkg_kernel_dump.o $(OUTPUT)/rxe_pkg_kernel.skel.h

[ ... ]

> diff --git a/tools/testing/selftests/rdma/rxe_pkg_common.h b/tools/testing/selftests/rdma/rxe_pkg_common.h
> new file mode 100644
> index 0000000000000..0dca4147d5f9a
> --- /dev/null
> +++ b/tools/testing/selftests/rdma/rxe_pkg_common.h
> @@ -0,0 +1,17 @@
> +// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
> +/*
> + * Copyright (c) 2026 Zhu Yanjun <yanjun.zhu@linux.dev>
> + */
> +
> +// rxe_pkg_common.h
> +#ifndef RXE_PKG_COMMON_H
> +#define RXE_PKG_COMMON_H
> +
> +#define MAX_PKT_LEN 512
> +
> +struct pkg_event {
> +	__u32 len;
> +	__u8 data[MAX_PKT_LEN];
> +};
> +
> +#endif
> diff --git a/tools/testing/selftests/rdma/rxe_pkg_kernel_dump.c b/tools/testing/selftests/rdma/rxe_pkg_kernel_dump.c
> new file mode 100644
> index 0000000000000..f465b52e7366c
> --- /dev/null
> +++ b/tools/testing/selftests/rdma/rxe_pkg_kernel_dump.c
> @@ -0,0 +1,77 @@
> +// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
> +/*
> + * Copyright (c) 2026 Zhu Yanjun <yanjun.zhu@linux.dev>
> + */
> +
> +#include <linux/bpf.h>
> +#include <bpf/bpf_helpers.h>
> +#include <linux/if_ether.h>
> +#include <linux/ip.h>
> +#include <arpa/inet.h>
> +
> +#include "rxe_pkg_common.h"
> +
> +struct {
> +	__uint(type, BPF_MAP_TYPE_RINGBUF);
> +	__uint(max_entries, 256 * 1024);/* Shared by all CPUs */
> +} rb SEC(".maps");
> +
> +SEC("netkit/primary")
> +int handle_netkit_ingress(struct __sk_buff *skb)
> +{
> +	/* Get the header pointer of skb */
> +	void *data_end = (void *)(long)skb->data_end;
> +	void *data = (void *)(long)skb->data;
> +
> +	struct ethhdr *eth = data;
> +
> +	/* Verify the data */
> +	if (data + sizeof(*eth) > data_end)
> +		return BPF_OK;
> +
> +	/* Check if it is IP skb */
> +	if (eth->h_proto == htons(ETH_P_IP)) {

[Severity: Medium]
Will compiling rxe_pkg_kernel_dump.c with -target bpf and including the
userspace <arpa/inet.h> header break the build or fail verification on
standard multiarch and 64-bit distributions?

When compiling with -target bpf in tools/testing/selftests/rdma/Makefile,
clang does not define host architecture macros such as __x86_64__. Including
glibc's <arpa/inet.h> pulls in <features.h> and <gnu/stubs.h>, which falls
back to <gnu/stubs-32.h> and fails on 64-bit hosts without 32-bit multilib
headers, or fails to find multiarch headers such as <sys/cdefs.h> and
<asm/types.h> without clang system include paths. In addition, calling
htons() instead of bpf_htons() from <bpf/bpf_endian.h> leaves an unresolved
external symbol call in handle_netkit_ingress() that libbpf and the bpf
verifier reject when loading the skeleton.

Also in tools/testing/selftests/rdma/Makefile, the build rules pass raw
unexported kernel headers via -I../../../../include/uapi, link against the
host -lbpf instead of building the in-tree tools/lib/bpf (which provides
bpf_program__attach_netkit()), omit rxe_pkg_common.h from the prerequisites,
and redirect bpftool output directly to $@ without cleaning up a partial
rxe_pkg_kernel.skel.h on failure. Should rxe_pkg_kernel_dump.c use
<bpf/bpf_endian.h> and bpf_htons(), and should the makefile build against
the in-tree libbpf and exported uapi headers?

> +		struct iphdr *iph = data + sizeof(*eth);
> +
> +		if ((void *)iph + sizeof(*iph) <= data_end) {
> +			bpf_printk("Got IP packet from netkit ingress, %d\n", skb->len);
> +
> +			__u32 len = skb->len;
> +
> +			/* It is a rping test, the size of packets should be
> +			 * less than MAX_PKT_LEN
> +			 */
> +			if (len == 0 || len > MAX_PKT_LEN) {
> +				bpf_printk("len is not in 0..MAX_PKT_LEN\n");
> +				return BPF_OK;
> +			}
> +
> +			struct pkg_event *e;
> +
> +			/* The len is fixed to MAX_PKT_LEN */
> +			e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0);
> +
> +			if (!e) {
> +				bpf_printk("bpf_ringbuf_reserve fails\n");
> +				return BPF_OK;
> +			}
> +
> +			e->len = len;
> +
> +			if (bpf_skb_load_bytes(skb, 0, e->data, len) < 0) {
> +				bpf_ringbuf_discard(e, 0);
> +				bpf_printk("bpf_skb_load_bytes fails\n");
> +				return BPF_OK;
> +			}
> +
> +			bpf_ringbuf_submit(e, 0);
> +		}
> +	}
> +
> +	/* BPF_OK:	Pass
> +	 * BPF_DROP:	Drop skb
> +	 */
> +	return BPF_OK;
> +}
> +
> +char _license[] SEC("license") = "GPL";
> diff --git a/tools/testing/selftests/rdma/rxe_pkg_user_dump.c b/tools/testing/selftests/rdma/rxe_pkg_user_dump.c
> new file mode 100644
> index 0000000000000..0fe6d00c198e3
> --- /dev/null
> +++ b/tools/testing/selftests/rdma/rxe_pkg_user_dump.c
> @@ -0,0 +1,1342 @@
> +// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
> +/*
> + * Copyright (c) 2026 Zhu Yanjun <yanjun.zhu@linux.dev>
> + */
> +
> +#include <stdio.h>
> +#include <sys/resource.h>
> +#include <net/if.h>
> +#include "rxe_pkg_kernel.skel.h"
> +
> +#include <linux/ip.h>
> +#include <linux/udp.h>
> +#include <ctype.h>
> +
> +#include <net/ethernet.h>
> +#include <arpa/inet.h>
> +
> +#include "rxe_pkg_common.h"
> +
> +typedef unsigned char __u8;
> +typedef unsigned short __u16;
> +typedef unsigned int __u32;
> +typedef unsigned long long __u64;

[Severity: Low]
Does redefining __u64 in tools/testing/selftests/rdma/rxe_pkg_user_dump.c
cause a conflicting type error on ppc64le?

Because <linux/ip.h> and <linux/udp.h> are included above, <linux/types.h>
is already pulled in. On 64-bit powerpc (ppc64le, which the makefile
explicitly supports) as well as mips64 and alpha, the userspace uapi headers
use <asm-generic/int-l64.h> and define __u64 as unsigned long, conflicting
with this unsigned long long typedef. Could these duplicate __u8 through
__u64 typedefs be dropped in favor of <linux/types.h>?

[ ... ]

> +static void print_bpf_output(const void *data, __u32 size)
> +{
> +	/* Validate size covers IP and UDP headers */
> +	if (size < sizeof(struct iphdr) + sizeof(struct udphdr))
> +		return;
> +
> +	struct iphdr *iph = (struct iphdr *)data;
> +
> +	if (iph->protocol != IPPROTO_UDP)
> +		return;
> +
> +	struct udphdr *udp_hdr = (struct udphdr *)(data + sizeof(struct iphdr));
> +
> +	if (ntohs(udp_hdr->dest) != ROCEV2_PORT)
> +		return;
> +
> +	print_ipaddr(iph);
> +	printf("udp hdr:\n");
> +	print_udphdr(udp_hdr);
> +
> +	size_t base_hdr_size = sizeof(struct iphdr) + sizeof(struct udphdr)
> +				+ sizeof(struct rxe_bth);
> +
> +	if (size < base_hdr_size)
> +		return;
> +
> +	struct rxe_bth *bth = (struct rxe_bth *)(data + sizeof(struct iphdr)
> +				+ sizeof(struct udphdr));
> +
> +	/* Validate opcode bounds and ensure name exists to prevent NULL dereference */
> +	if (bth->opcode >= 256 || !rxe_opcode[bth->opcode].name) {
> +		printf("Unknown or unpopulated opcode: 0x%x\n", bth->opcode);
> +		return;
> +	}
> +
> +	printf("%s\n", rxe_opcode[bth->opcode].name);
> +	print_bth(bth);
> +
> +	for (int i = RXE_BTH + 1; i < NUM_HDR_TYPES; i++) {
> +		int offset = rxe_opcode[bth->opcode].offset[i];
> +
> +		if (offset && (size >= sizeof(struct iphdr) + sizeof(struct udphdr) + offset)) {
> +			char *cur_p = (char *)bth + offset;
> +
> +			switch (i) {
> +			case	RXE_RETH:
> +				printf("RXE_RETH\n");
> +				struct rxe_reth *reth = (struct rxe_reth *)cur_p;
> +
> +				print_reth(reth);
> +				break;

[ ... ]

> +			case	RXE_PAYLOAD:
> +				printf("RXE_PAYLOAD\n");
> +				int len = ntohs(udp_hdr->len) - rxe_opcode[bth->opcode].length;
> +
> +				for (int j = 0; j < len; j++) {
> +					if (isprint(cur_p[j]))
> +						printf("%c", cur_p[j]);
> +					else
> +						printf("%02x", cur_p[j]);
> +				}
> +				printf("\n");
> +				break;
> +			default:
> +				break;
> +			}
> +		}
> +	}
> +
> +	printf("\n");
> +}
> +
> +static int handle_event(void *ctx, void *data, unsigned long data_sz)
> +{
> +	const struct pkg_event *e = data;
> +
> +	if (data_sz < sizeof(*e))
> +		return -1;
> +
> +	print_bpf_output(e->data + sizeof(struct ethhdr),
> +				e->len - sizeof(struct ethhdr));
> +
> +	return 0;
> +}

[Severity: Medium]
Can handle_event() -> print_bpf_output() read uninitialized ring-buffer
memory and overflow past e->data[]?

In handle_netkit_ingress(), bpf_ringbuf_reserve() allocates a struct
pkg_event without zeroing e->data, and bpf_skb_load_bytes() only writes the
first e->len bytes. Then in handle_event(), e->len is not checked against
sizeof(struct ethhdr) or the capacity of e->data before subtracting
sizeof(struct ethhdr) and calling print_bpf_output().

Inside print_bpf_output(), there are several bounds issues:

1. The header loop check:

	if (offset && (size >= sizeof(struct iphdr) + sizeof(struct udphdr) + offset))

only verifies that the start pointer cur_p = (char *)bth + offset is within
size, without checking that the full extended header struct at cur_p (such as
struct rxe_reth or struct rxe_atmeth) fits within size.

2. In the payload case:

	int len = ntohs(udp_hdr->len) - rxe_opcode[bth->opcode].length;

ntohs(udp_hdr->len) includes the 8-byte struct udphdr as well as the 4-byte
rocev2 icrc (and any bth pad count), and len is never clamped to the
remaining captured bytes in size. Because sizeof(struct udphdr) is not
subtracted, cur_p + len always extends at least 8 bytes past data + size,
reading uninitialized ring-buffer memory on every packet and reading
out-of-bounds past struct pkg_event whenever e->len is between 505 and 512
(or up to 64 kilobytes past e->data if udp_hdr->len is larger than the
captured size).

3. In rxe_opcode[], header-only opcodes such as acknowledge and rdma read
request define a non-zero payload offset, so the 4-byte trailing icrc
satisfies size >= sizeof(struct iphdr) + sizeof(struct udphdr) + offset and
causes print_bpf_output() to print the icrc plus 8 uninitialized or
out-of-bounds bytes as payload.

4. Because cur_p is a signed char *, passing cur_p[j] with bit 7 set is
undefined behavior in isprint() and sign-extends to a negative int in
printf("%02x", cur_p[j]). Should cur_p be unsigned char * (or cast to
unsigned char)?

> +
> +int main(void)
> +{

[ ... ]

> +	err = 0;
> +
> +	while (1) {
> +		err = ring_buffer__poll(ringbuf, 100 /* timeout */);
> +		if (err < 0 && err != -EINTR) {
> +			fprintf(stderr, "poll error\n");
> +			break;
> +		}
> +	}
> +
> +cleanup:
> +	ring_buffer__free(ringbuf);
> +	rxe_pkg_kernel_dump__destroy(skel);
> +	return err;
> +}

[Severity: Medium]
What happens when rxe_netkit_dump is run in the background with stdout
redirected to a file or pipe and is then stopped by a signal?

In main(), the while (1) loop around ring_buffer__poll() has no signal
handler for termination signals and only breaks when err < 0 && err != -EINTR,
making the cleanup label unreachable on normal termination. In addition,
print_bpf_output() writes via printf() without flushing stdout (or setting
line buffering via setvbuf()), so when stdout is redirected to a non-tty file
or pipe, stdio block-buffers the output and discards captured packet dumps
when the process is killed. Could main() install a signal handler to break
out of the poll loop and flush stdout after dumping each packet?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261021203115.54560-1-yanjun.zhu@linux.dev?part=1

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH v3 2/2] selftests/rdma: Add netkit-based automated test script for RXE
  2026-10-21 20:31 ` [PATCH v3 2/2] selftests/rdma: Add netkit-based automated test script " Zhu Yanjun
@ 2026-10-21 20:46   ` sashiko-bot
  0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-10-21 20:46 UTC (permalink / raw)
  To: Zhu Yanjun, linux-rdma

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [Medium] selftests/rdma: rxe_pkg_netkit.sh discards output and ignores dump errors
- [Medium] selftests/rdma: unguarded tracefs access under set -e in rxe_pkg_netkit.sh
- [Medium] selftests/rdma: broken INT/TERM trap and dump teardown in cleanup()
- [Medium] selftests/rdma: leaked background rping server process in cleanup()
- [Medium] selftests/rdma: out-of-bounds and uninitialized read in print_bpf_output()
- [Medium] selftests/rdma: signed char passed to isprint() and printf() in print_bpf_output()
--

commit 550e551c2277aaac647ca619a6a0455e8de798af
Author: Zhu Yanjun <yanjun.zhu@linux.dev>

selftests/rdma: Add netkit-based automated test script for RXE

This commit adds an automated selftest script (rxe_pkg_netkit.sh) for the
Soft-RoCE (rxe) driver over netkit interfaces. It configures an isolated
network namespace with a netkit link pair, attaches rxe devices, and runs an
rping client/server test alongside packet dump and ftrace collection.

> diff --git a/tools/testing/selftests/rdma/rxe_pkg_netkit.sh b/tools/testing/selftests/rdma/rxe_pkg_netkit.sh
> new file mode 100755
> index 0000000000000..181e2d85eed76
> --- /dev/null
> +++ b/tools/testing/selftests/rdma/rxe_pkg_netkit.sh
> @@ -0,0 +1,81 @@

[ ... ]

> +exec > /dev/null
> +
> +set -e
> +
> +CUR_WD="$(dirname "$0")"
> +[ "$CUR_WD" = "." ] && CUR_WD="$(pwd)"
> +
> +cleanup() {
> +    set +e
> +    [ -n "$DUMP_PID" ] && kill -9 "$DUMP_PID" 2>/dev/null
> +    unset DUMP_PID
> +
> +    rdma link del rxe1 2>/dev/null
> +    ip link del nk0 2>/dev/null
> +    ip netns delete ns0 2>/dev/null
> +}
> +
> +trap cleanup EXIT INT TERM

[Severity: Medium]
What happens in tools/testing/selftests/rdma/rxe_pkg_netkit.sh when the
script receives an interrupt or termination signal (such as on a kselftest
runner timeout)?

When cleanup() runs for an interrupt or termination trap, it executes
set +e in the shell's global environment, tears down rxe1, nk0, and ns0,
and returns without calling exit. In POSIX /bin/sh, returning from an
interrupt or termination trap resumes execution at the command following
the interrupted one. Because set +e remains in effect globally, all
subsequent commands (rping -c, rdma link del, ip link del, ip netns delete)
fail silently against the already-removed devices and namespace, and if the
final cat /sys/kernel/debug/tracing/trace succeeds, the script exits with
status 0 (a false pass) before running cleanup() a second time on exit.

Also, on the normal execution path:

    # Tear down RDMA links and netkit devices
    ip netns exec ns0 rdma link del rxe0
    rdma link del rxe1
    ip link del nk0
    ip netns delete ns0

the script deletes nk0 while rxe_netkit_dump is still running and attached
to nk0, duplicating the teardown in cleanup(), and then cleanup() kills
rxe_netkit_dump with kill -9 without waiting for it. Using kill -9 prevents
rxe_netkit_dump from catching the signal in sig_handler() and running its
cleanup path (ring_buffer__free() and rxe_pkg_kernel_dump__destroy()) or
flushing its standard output buffers.

Should cleanup() send a normal termination signal, wait for the child
process, and rely on the exit trap for teardown while having interrupt and
termination traps exit with a non-zero status?

> +
> +# Clear tracing buffer
> +echo > /sys/kernel/debug/tracing/trace

[Severity: Medium]
Can this access to /sys/kernel/debug/tracing/trace in
tools/testing/selftests/rdma/rxe_pkg_netkit.sh cause a hard test failure on
systems where debugfs or tracefs is not mounted at /sys/kernel/debug/tracing
or where kernel tracing is disabled in the kernel configuration?

Earlier in the script, missing prerequisites (root privileges, ip, rdma,
rping, netkit, and rdma_rxe) cause the test to skip with exit code 4.
However, after set -e is enabled, writing to and reading from
/sys/kernel/debug/tracing/trace will abort the script with exit status 1 if
that path is unavailable. Should the script check whether
/sys/kernel/debug/tracing/trace is available and writable before running, or
skip the test with exit code 4 when tracefs is not present?

> +
> +# Create network namespace and netkit peer links
> +ip netns add ns0
> +ip link add nk0 type netkit mode l2 peer name nk1
> +ip link set nk0 up
> +ip link set nk1 up
> +
> +# Configure namespace interface
> +ip link set nk1 netns ns0
> +ip netns exec ns0 ip addr add 10.0.0.1/24 dev nk1
> +ip netns exec ns0 ip link set nk1 up
> +
> +# Configure host interface
> +ip addr add 10.0.0.2/24 dev nk0
> +
> +# Start dump utility in background
> +"$CUR_WD/rxe_netkit_dump" &
> +DUMP_PID=$!

[Severity: Medium]
When rxe_pkg_netkit.sh launches rxe_netkit_dump here and runs rping over
the netkit and rxe links, can handle_event() -> print_bpf_output() in
tools/testing/selftests/rdma/rxe_pkg_user_dump.c read uninitialized ring
buffer memory and read out of bounds past the pkg_event buffer?

In tools/testing/selftests/rdma/rxe_pkg_kernel_dump.c,
handle_netkit_ingress() reserves a pkg_event entry in the ring buffer
without zeroing e->data and copies only len bytes:

    e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0);
    ...
    e->len = len;

    if (bpf_skb_load_bytes(skb, 0, e->data, len) < 0) {

Then in tools/testing/selftests/rdma/rxe_pkg_user_dump.c, handle_event()
passes e->len - sizeof(struct ethhdr) directly to print_bpf_output() without
checking that e->len is at least sizeof(struct ethhdr) or at most
MAX_PKT_LEN:

    static int handle_event(void *ctx, void *data, unsigned long data_sz)
    {
        const struct pkg_event *e = data;

        if (data_sz < sizeof(*e))
            return -1;

        print_bpf_output(e->data + sizeof(struct ethhdr),
                    e->len - sizeof(struct ethhdr));

        return 0;
    }

Inside print_bpf_output(), the header loop checks only that the start offset
of each header is within size, without verifying that the header structure
itself fits within the remaining captured bytes:

    for (int i = RXE_BTH + 1; i < NUM_HDR_TYPES; i++) {
        int offset = rxe_opcode[bth->opcode].offset[i];

        if (offset && (size >= sizeof(struct iphdr) + sizeof(struct udphdr) + offset)) {
            char *cur_p = (char *)bth + offset;

And when handling the payload in print_bpf_output(), len is computed by
subtracting rxe_opcode[bth->opcode].length from ntohs(udp_hdr->len) without
subtracting sizeof(struct udphdr) (8 bytes) and without clamping len to the
captured buffer size:

            case RXE_PAYLOAD:
                printf("RXE_PAYLOAD\n");
                int len = ntohs(udp_hdr->len) - rxe_opcode[bth->opcode].length;

                for (int j = 0; j < len; j++) {
                    if (isprint(cur_p[j]))
                        printf("%c", cur_p[j]);
                    else
                        printf("%02x", cur_p[j]);
                }

Because udp_hdr->len includes the 8-byte UDP header while cur_p starts at
(char *)bth + offset (after the UDP header and RoCE headers), len is always
8 bytes larger than the remaining packet bytes. On every packet with a
payload, cur_p[j] reads 8 bytes past the end of the captured packet into
uninitialized ring buffer memory (or past e->data[MAX_PKT_LEN] if the
packet length is near MAX_PKT_LEN).

[Severity: Medium]
Also in tools/testing/selftests/rdma/rxe_pkg_user_dump.c, can passing
cur_p[j] to isprint() and printf() in print_bpf_output() trigger undefined
behavior and sign-extension in the hex output?

In print_bpf_output(), cur_p is declared as a plain char pointer:

    char *cur_p = (char *)bth + offset;
    ...
    for (int j = 0; j < len; j++) {
        if (isprint(cur_p[j]))
            printf("%c", cur_p[j]);
        else
            printf("%02x", cur_p[j]);
    }

Because userspace tools under tools/testing/selftests/rdma/ are compiled
without -funsigned-char, char is signed on architectures like x86_64. Any
payload or trailing CRC byte with the high bit set (>= 0x80) is negative
when accessed through cur_p[j], which causes negative indexing in isprint()
and sign-extends to a 32-bit negative integer in printf("%02x", cur_p[j]),
printing 8 hex characters (such as ffffff80) instead of 2. Should cur_p[j]
be cast to unsigned char before calling isprint() and printf()?

> +
> +# Add RXE links over netkit devices
> +ip netns exec ns0 rdma link add rxe0 type rxe netdev nk1
> +rdma link add rxe1 type rxe netdev nk0
> +
> +# Start rping server in namespace
> +ip netns exec ns0 rping -s -a 10.0.0.1 -C 3 &
> +
> +# Add wait time
> +sleep 1
> +
> +# Run rping client on host
> +rping -c -a 10.0.0.1 -C 3
> +
> +# Tear down RDMA links and netkit devices
> +ip netns exec ns0 rdma link del rxe0

[Severity: Medium]
Does tools/testing/selftests/rdma/rxe_pkg_netkit.sh leak the background
rping server process if rping -c fails or if the script exits early?

When rping -s is started in the background inside ns0, its process ID is
not saved, and cleanup() neither kills it nor deletes rxe0 inside ns0:

    cleanup() {
        set +e
        [ -n "$DUMP_PID" ] && kill -9 "$DUMP_PID" 2>/dev/null
        unset DUMP_PID

        rdma link del rxe1 2>/dev/null
        ip link del nk0 2>/dev/null
        ip netns delete ns0 2>/dev/null
    }

If rping -c fails to connect, the background rping -s process remains
blocked waiting for a connection, pinning the ns0 network namespace and
rxe0 device in the kernel even after ip netns delete ns0 unlinks the
namespace mount, and keeping inherited file descriptors open (which can
cause the kselftest runner pipeline to hang until timeout). Even when
rping -c succeeds, ip netns exec ns0 rdma link del rxe0 runs immediately
without waiting for the background rping -s process to finish disconnecting.

Should the script record the rping server PID, wait for it after rping -c
completes, and ensure cleanup() terminates the server and deletes rxe0?

> +rdma link del rxe1
> +ip link del nk0
> +ip netns delete ns0
> +
> +# Display kernel trace output
> +cat /sys/kernel/debug/tracing/trace

[Severity: Medium]
Does tools/testing/selftests/rdma/rxe_pkg_netkit.sh verify the packet dump
or tracing output, or does redirecting standard output to /dev/null cause
dump failures to go unnoticed?

Near the top of rxe_pkg_netkit.sh, standard output is redirected for the
entire script and all child processes:

    exec > /dev/null

As a result, both the RoCEv2 packet dump printed by rxe_netkit_dump and the
cat /sys/kernel/debug/tracing/trace output at the end of the script are
discarded to /dev/null without checking that any packets or trace entries
were captured. In addition, when rxe_netkit_dump is started in the
background:

    # Start dump utility in background
    "$CUR_WD/rxe_netkit_dump" &
    DUMP_PID=$!

the script never checks whether rxe_netkit_dump existed, succeeded in
loading and attaching its BPF program to nk0 before rping starts, or exited
with an error. Even if rxe_netkit_dump fails immediately or captures zero
packets, the script still exits with 0. Should the script verify that
rxe_netkit_dump attaches and captures the expected RoCEv2 packets and trace
output?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261021203115.54560-1-yanjun.zhu@linux.dev?part=2

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-10-21 20:46 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-21 20:31 [PATCH v3 0/2] selftests/rdma: Add an eBPF-based testcase using netkit for RXE Zhu Yanjun
2026-10-21 20:31 ` [PATCH v3 1/2] selftests/rdma: Introduce eBPF kernel and user dump utilities " Zhu Yanjun
2026-10-21 20:46   ` sashiko-bot
2026-10-21 20:31 ` [PATCH v3 2/2] selftests/rdma: Add netkit-based automated test script " Zhu Yanjun
2026-10-21 20:46   ` sashiko-bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox