From: sashiko-bot@kernel.org
To: "Zhu Yanjun" <yanjun.zhu@linux.dev>, linux-rdma@vger.kernel.org
Subject: Re: [PATCH v5 1/2] selftests/rdma: Introduce eBPF kernel and user dump utilities for RXE
Date: Sat, 31 Oct 2026 17:47:13 +0000 [thread overview]
Message-ID: <sashiko-outbox-192070@kernel.org> (raw)
In-Reply-To: <20261031173017.20691-2-yanjun.zhu@linux.dev>
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [Medium] selftests/rdma: broken BPF/UAPI include paths, arpa/inet.h, and skel rule
- [Medium] selftests/rdma: incompatible callback signature in handle_event() on 32-bit
- [Medium] selftests/rdma: print_bpf_output() includes ICRC and pad bytes in payload
- [Medium] selftests/rdma: fixed IPv4 header size and header loop order in print_bpf_output()
- [Medium] selftests/rdma: missing -mcpu=v3 in Makefile breaks BPF verification
- [Low] selftests/rdma: u64 printf format mismatch and __be32 return in immdt_imm()
- [Low] selftests/rdma: duplicated RXE tables/wrappers and hardcoded nk0 in dump tool
--
commit f654ac52160578ed80b6042ffb2064d0f8f8a7d7
Author: Zhu Yanjun <yanjun.zhu@linux.dev>
selftests/rdma: Introduce eBPF kernel and user dump utilities for RXE
This commit adds eBPF-based kernel and user-space packet dump utilities
for Soft-RoCE (RXE) traffic over netkit devices in the RDMA selftests.
> diff --git a/tools/testing/selftests/rdma/Makefile b/tools/testing/selftests/rdma/Makefile
> index a91c14c45006e..f7783c20f40db 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 rxe_pkg_common.h
> + $(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 $(OUTPUT)/rxe_pkg_kernel.skel.h.tmp
[Severity: Medium]
Could these Makefile rules and include flags cause build failures across
out-of-tree, multiarch, and -Werror environments, or leave a corrupted
skeleton header behind if bpftool fails?
In tools/testing/selftests/rdma/Makefile, INCLUDES hardcodes
-I../../../../usr/include and -I../../../../include/uapi instead of using
$(KHDR_INCLUDES) provided by ../lib.mk. Hardcoding ../../../../usr/include
breaks out-of-tree builds (O= or KBUILD_OUTPUT=), and passing the raw
unexported -I../../../../include/uapi path to userspace compilation triggers
the #warning in include/uapi/linux/types.h when __EXPORTED_HEADERS__ is not
defined (failing under USERCFLAGS=-Werror) and fails to resolve generated
<asm/types.h> and <asm/byteorder.h> headers when in-tree headers have not
been exported.
In addition, the recipe for $(OUTPUT)/rxe_pkg_kernel.skel.h redirects stdout
directly to $@:
$(BPFTOOL) gen skeleton $< > $@
If bpftool is missing or returns an error, the shell still creates an empty
$(OUTPUT)/rxe_pkg_kernel.skel.h file. Because that empty file satisfies
make's timestamp check on subsequent runs, compiling rxe_pkg_user_dump.c
then fails with undeclared skeleton symbols (even though EXTRA_CLEAN lists
$(OUTPUT)/rxe_pkg_kernel.skel.h.tmp, the rule does not write to $@.tmp and
mv it into place).
> 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..2030529f94af7
> --- /dev/null
> +++ b/tools/testing/selftests/rdma/rxe_pkg_kernel_dump.c
> @@ -0,0 +1,78 @@
> +// 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 <bpf/bpf_endian.h>
> +#include <linux/if_ether.h>
> +#include <linux/ip.h>
> +#include <arpa/inet.h>
[Severity: Medium]
Is <arpa/inet.h> needed in tools/testing/selftests/rdma/rxe_pkg_kernel_dump.c?
Because handle_netkit_ingress() uses bpf_htons() from <bpf/bpf_endian.h>,
<arpa/inet.h> is unused. Including a userspace libc header in a translation
unit compiled with clang -target bpf drops host architecture macros (such as
__x86_64__) and multiarch include paths (/usr/include/<arch>-linux-gnu),
which causes glibc headers (<features.h>, <sys/cdefs.h>, <gnu/stubs.h>) to
fail to locate <sys/cdefs.h> on multiarch distributions or fall back to
<gnu/stubs-32.h> and fail on 64-bit hosts without 32-bit glibc development
headers installed.
[ ... ]
> +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 == bpf_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) {
[Severity: Medium]
Will handle_netkit_ingress() be rejected by the BPF verifier when compiled
without -mcpu=v3?
In tools/testing/selftests/rdma/Makefile, $(OUTPUT)/rxe_pkg_kernel_dump.o is
built with:
$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(ARCH) $(INCLUDES) -c $< -o $@
Without -mcpu=v3, clang defaults to the BPF v1 ISA, which lacks 32-bit ALU
and JMP32 instructions. At -O2, clang lowers the 32-bit check
(if (len == 0 || len > MAX_PKT_LEN)) into a 64-bit addition followed by a
64-bit shift pair to zero-extend the lower 32 bits before the conditional
jump:
r1 = r6
r1 += -1
r1 <<= 32
r1 >>= 32
if r1 > 511 goto ...
In the kernel BPF verifier, adjust_reg_min_max_vals() preserves dst_reg->id
with BPF_ADD_CONST64 across BPF_ADD and BPF_SUB, but clears dst_reg->id via
clear_scalar_id() on BPF_LSH (r1 <<= 32). Because r1->id is 0 at the
conditional branch, check_cond_jmp_op() cannot propagate the [1, 512] bounds
back to r6 (len), leaving len with its initial umin=0 and umax=U32_MAX
bounds. When bpf_skb_load_bytes(skb, 0, e->data, len) is verified,
check_mem_size_reg() sees reg_umin(size_reg) == 0 with zero_size_allowed ==
false (and reg_umax exceeding sizeof(e->data)) and rejects loading the
program with -EACCES.
Should the clang invocation in Makefile pass -mcpu=v3 (matching the other BPF
selftests)?
> 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..4d3fede105f73
> --- /dev/null
> +++ b/tools/testing/selftests/rdma/rxe_pkg_user_dump.c
[ ... ]
> +static inline void print_reth(const 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");
> +}
[ ... ]
> +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 __be32 immdt_imm(struct rxe_immdt *immdt)
> +{
> + return ntohl(immdt->imm);
> +}
[Severity: Low]
Will printing u64 (__u64) values with %llx in print_reth(), print_atmeth(),
and print_atmack() produce -Wformat warnings on 64-bit architectures such as
ppc64le where __u64 in userspace is unsigned long rather than unsigned long
long? Also, should immdt_imm() return u32 instead of __be32 since it converts
immdt->imm to host byte order with ntohl()?
[ ... ]
> +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;
> +
> + const struct iphdr *iph = (const struct iphdr *)data;
> +
> + if (iph->protocol != IPPROTO_UDP)
> + return;
> +
> + const struct udphdr *udp_hdr = (const 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;
> +
> + const 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)) {
> + const unsigned char *cur_p = (const unsigned char *)bth + offset;
> + size_t remaining_size = size - ((const void *)cur_p - data);
> +
> + if (i < ARRAY_SIZE(rxe_hdr_sizes) && rxe_hdr_sizes[i] > 0) {
> + if (remaining_size < rxe_hdr_sizes[i])
> + break;
> + }
[Severity: Medium]
Does print_bpf_output() misparse packets when IPv4 options are present or
when headers appear in a different order than enum rxe_hdr_type?
First, print_bpf_output() computes the UDP and BTH offsets using
sizeof(struct iphdr) instead of validating iph->ihl >= 5 and advancing by
iph->ihl * 4.
Second, the loop iterates i from RXE_BTH + 1 to NUM_HDR_TYPES in enum
rxe_hdr_type order (where RXE_RETH = 3 precedes RXE_RDETH = 8 and
RXE_DETH = 9) rather than packet offset order. For RD opcodes such as
IB_OPCODE_RD_RDMA_WRITE_FIRST, RDETH and DETH are placed before RETH in the
packet, so breaking out of the loop when remaining_size < rxe_hdr_sizes[i]
skips headers with larger enum values that actually lie earlier in the
packet.
Third, bth->opcode is a __u8, so the bth->opcode >= 256 check is always
false.
[ ... ]
> + case RXE_PAYLOAD:
> + printf("RXE_PAYLOAD\n");
> + int expected_len = (int)ntohs(udp_hdr->len)
> + - (int)sizeof(struct udphdr)
> + - rxe_opcode[bth->opcode].length;
> + int len = (expected_len < (int)remaining_size) ?
> + expected_len : (int)remaining_size;
> +
> + if (len < 0)
> + len = 0;
> +
> + 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;
[Severity: Medium]
Does the RXE_PAYLOAD calculation include the trailing BTH pad bytes and the
4-byte RoCEv2 ICRC as part of the payload?
Every RoCEv2 packet ends with 0..3 BTH pad bytes ((bth->flags & 0x30) >> 4)
and a 4-byte Invariant CRC (RXE_ICRC_SIZE, see payload_size() in
drivers/infiniband/sw/rxe/rxe_hdr.h). In addition, rxe_opcode[] sets a
non-zero .offset[RXE_PAYLOAD] even for opcodes that carry no payload, such
as IB_OPCODE_RC_ACKNOWLEDGE and IB_OPCODE_RC_RDMA_READ_REQUEST. Because
expected_len and remaining_size do not subtract RXE_ICRC_SIZE (4) and the BTH
padding length, print_bpf_output() prints 4 bytes of ICRC as RXE_PAYLOAD on
payload-less packets and appends padding and ICRC bytes to actual payloads.
> + 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;
> +
> + if (e->len < sizeof(struct ethhdr) || e->len > sizeof(e->data))
> + return -1;
> +
> + print_bpf_output(e->data + sizeof(struct ethhdr),
> + e->len - sizeof(struct ethhdr));
> +
> + return 0;
> +}
[ ... ]
> + 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);
[Severity: Medium]
Will passing handle_event() to ring_buffer__new() fail to build on 32-bit
architectures?
In tools/lib/bpf/libbpf.h, ring_buffer_sample_fn is defined as:
typedef int (*ring_buffer_sample_fn)(void *ctx, void *data, size_t size);
However, handle_event() declares its third parameter as unsigned long
data_sz. On 32-bit architectures where size_t is unsigned int, passing
handle_event to ring_buffer__new() fails compilation with
-Wincompatible-pointer-types.
[Severity: Low]
This isn't a bug, but rxe_pkg_user_dump.c duplicates over 1,000 lines of
RXE header definitions, IB_OPCODE_* enums, rxe_opcode[] table entries, and
trivial wrapper functions (__bth_opcode()/bth_opcode(), etc.) from
drivers/infiniband/sw/rxe/, and rxe_opcode[] has already drifted from the
kernel table (omitting newer opcodes such as IB_OPCODE_RC_FLUSH and
IB_OPCODE_RC_ATOMIC_WRITE). In addition, main() hardcodes the interface name
"nk0" in if_nametoindex("nk0"). Could the interface name be passed via
command-line arguments (defaulting to "nk0"), and can the duplicated
definitions and wrappers in rxe_pkg_user_dump.c be trimmed or shared?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261031173017.20691-1-yanjun.zhu@linux.dev?part=1
next prev parent reply other threads:[~2026-10-31 17:47 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-31 17:30 [PATCH v5 0/2] selftests/rdma: Add an eBPF-based testcase using netkit for RXE Zhu Yanjun
2026-10-31 17:30 ` [PATCH v5 1/2] selftests/rdma: Introduce eBPF kernel and user dump utilities " Zhu Yanjun
2026-10-31 17:47 ` sashiko-bot [this message]
2026-10-31 17:30 ` [PATCH v5 2/2] selftests/rdma: Add netkit-based automated test script " Zhu Yanjun
2026-10-31 17:45 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-192070@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=linux-rdma@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=yanjun.zhu@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.