From: "Alexei Starovoitov" <alexei.starovoitov@gmail.com>
To: "Daniel Borkmann" <daniel@iogearbox.net>, <memxor@gmail.com>
Cc: <bpf@vger.kernel.org>
Subject: Re: [PATCH bpf-next 2/2] selftests/bpf: Add JIT cBPF-related instruction tests
Date: Thu, 15 Oct 2026 19:08:24 +0000 [thread overview]
Message-ID: <DM5NCJVIY5GR.3KH59CI540I82@gmail.com> (raw)
In-Reply-To: <20261015171917.599025-2-daniel@iogearbox.net>
On Thu Oct 15, 2026 at 5:19 PM UTC, Daniel Borkmann wrote:
> The classic BPF cases of the removed test_bpf module ran their programs
> on a hand-built skb. Run them as socket filters instead via jit_insns_cbpf
> test suite. This covers all previous test_bpf cases as well as a few corner
> cases beyond that.
>
> # LDLIBS=-static PKG_CONFIG='pkg-config --static' ./vmtest.sh -- ./test_progs -t jit_insns_cbpf
> [...]
> #193/1 jit_insns_cbpf/classic:OK
> #193/2 jit_insns_cbpf/checker:OK
> #193/3 jit_insns_cbpf/frags:OK
> #193/4 jit_insns_cbpf/packets:OK
> #193/5 jit_insns_cbpf/blinded:OK
> #193 jit_insns_cbpf:OK
> Summary: 1/5 PASSED, 0 SKIPPED, 0/0 FAILED
>
> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
> ---
> [ Small fyi, the gso related tests I pushed here for net-next
> https://lore.kernel.org/netdev/20261015151033.591439-1-daniel@iogearbox.net/
> thus this concludes all of old test_bpf. ]
>
> .../selftests/bpf/prog_tests/jit_insns_cbpf.c | 2620 +++++++++++++++++
> 1 file changed, 2620 insertions(+)
> create mode 100644 tools/testing/selftests/bpf/prog_tests/jit_insns_cbpf.c
>
> diff --git a/tools/testing/selftests/bpf/prog_tests/jit_insns_cbpf.c b/tools/testing/selftests/bpf/prog_tests/jit_insns_cbpf.c
> new file mode 100644
> index 000000000000..5471c06605fe
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/jit_insns_cbpf.c
> @@ -0,0 +1,2620 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (c) 2026 Isovalent */
> +/*
> + * The classic BPF cases of the former test_bpf module, run as socket
> + * filters: the data cases on an AF_UNIX datagram pair, which takes any
> + * length down to zero and puts the tail of a long datagram into page
> + * frags, the cases reading the link layer or the metadata of a packet on
> + * a veth pair in their own netns. The return of a filter decides over the
> + * packet, zero drops it and any other value truncates it to that length,
> + * so the returns of a program get rewritten: the expected result stays,
> + * any other constant becomes zero and a returned accumulator is compared
> + * at the end, so that a wrong result shows as a dropped packet.
> + */
> +#include <test_progs.h>
> +#include <poll.h>
> +#include <sys/ioctl.h>
> +#include <linux/filter.h>
> +
> +#include "network_helpers.h"
> +#include "sysctl_helpers.h"
> +
> +#define MAX_K 0xffffffff
> +#define SKB_TYPE PACKET_OTHERHOST /* the frames go to a foreign MAC */
> +#define SKB_MARK 0x1234aaaa /* the mark of the sending socket */
> +#define SKB_VLAN_TCI 0xffff
> +#define SKB_VLAN_PRESENT 1
> +#define SKB_DEV_IFINDEX 0 /* that of the receiving veth */
> +#define SKB_DEV_TYPE 1 /* ARPHRD_ETHER, that of a veth */
> +/*
> + * A datagram of four pages splits into three pages of head and one page of
> + * frag data: what exceeds SKB_MAX_ALLOC, four pages less the shared info,
> + * goes into frags, rounded up to a page. The table cases take 4K pages, a
> + * larger page keeps their datagram linear; the sweep goes by the page size.
> + */
> +#define FRAG_OFF (3 * 4096) /* the frag data starts here */
> +#define SWEEP_SPAN 16 /* offsets swept either side of it */
> +#define NETNS "jit_insns_cbpf"
> +
> +static int page_size;
> +static int dgram_max; /* four pages */
> +
> +enum {
> + CB_FAIL = 1, /* the attach has to fail */
> + CB_PKT = 2, /* needs the link layer or the metadata of a packet */
> + CB_VLAN = 4, /* a tagged frame, the stack pulls the tag out */
> + CB_FRAG = 8, /* the length is the head, the frag data goes at its end */
> + CB_IFINDEX = 16, /* the result is the ifindex of the receiving veth */
> + CB_DSTMAC = 32, /* the frame goes to the MAC of the receiving veth */
> + CB_NOBLIND = 64, /* blinding grows the converted jump past 16 bits */
> + CB_RAW = 128, /* the returns stay, the kept length shows the result */
> +};
> +
> +struct cbpf_run {
> + int len;
> + __u32 result;
> +};
> +
> +struct cbpf_case {
> + const char *descr;
> + const struct sock_filter *insns;
> + int cnt;
> + int (*fill)(struct sock_filter *insns);
> + const __u8 *data;
> + int data_len;
> + const __u8 *frag;
> + int frag_len;
> + const struct cbpf_run *runs;
> + int nr_runs;
> + unsigned int flags;
> +};
> +
> +#define INSNS(...) \
> + (const struct sock_filter[]){ __VA_ARGS__ }, \
> + .cnt = ARRAY_SIZE(((const struct sock_filter[]){ __VA_ARGS__ }))
> +#define DATA(...) \
> + (const __u8[]){ __VA_ARGS__ }, \
> + .data_len = sizeof((const __u8[]){ __VA_ARGS__ })
> +#define FRAG(...) \
> + (const __u8[]){ __VA_ARGS__ }, \
> + .frag_len = sizeof((const __u8[]){ __VA_ARGS__ })
> +#define RUNS(...) \
> + (const struct cbpf_run[]){ __VA_ARGS__ }, \
> + .nr_runs = ARRAY_SIZE(((const struct cbpf_run[]){ __VA_ARGS__ }))
> +
> +/* The uapi forms are initializers, these are values */
> +#define STMT(code, k) ((struct sock_filter)BPF_STMT(code, k))
> +#define JUMP(code, k, jt, jf) ((struct sock_filter)BPF_JUMP(code, k, jt, jf))
> +
> +/* Jumps with gaps in between, the former __bpf_fill_ja() */
> +static int fill_ja(struct sock_filter *insns, int len, int plen)
> +{
> + int rlen = (len % plen) - 1, i, j;
> +
> + for (i = 0; i + plen < len; i += plen)
> + for (j = 0; j < plen; j++)
> + insns[i + j] = JUMP(BPF_JMP | BPF_JA, plen - 1 - j, 0, 0);
> + for (j = 0; j < rlen; j++)
> + insns[i + j] = JUMP(BPF_JMP | BPF_JA, rlen - 1 - j, 0, 0);
> + insns[len - 1] = STMT(BPF_RET | BPF_K, 0xababcbac);
> + return len;
> +}
> +
> +static int fill_ja_short(struct sock_filter *insns)
> +{
> + return fill_ja(insns, 12, 9);
> +}
> +
> +static int fill_ja_long(struct sock_filter *insns)
> +{
> + return fill_ja(insns, BPF_MAXINSNS, 68);
> +}
> +
> +static int fill_too_many(struct sock_filter *insns)
> +{
> + int i;
> +
> + for (i = 0; i <= BPF_MAXINSNS; i++)
> + insns[i] = STMT(BPF_RET | BPF_K, 0xfefefefe);
> + return BPF_MAXINSNS + 1;
> +}
> +
> +/*
> + * A jump over MSH loads, each of which becomes 18 instructions: the
> + * converted offset has to fit 16 bits, which 1820 of them do.
> + */
> +static int fill_jump_over_msh(struct sock_filter *insns, int nr)
> +{
> + int i;
> +
> + insns[0] = JUMP(BPF_JMP | BPF_JA, nr, 0, 0);
> + for (i = 1; i <= nr; i++)
> + insns[i] = STMT(BPF_LDX | BPF_B | BPF_MSH, 0);
> + insns[nr + 1] = STMT(BPF_RET | BPF_K, 0xabababab);
> + return nr + 2;
> +}
> +
> +static int fill_jump_over_all_msh(struct sock_filter *insns)
> +{
> + return fill_jump_over_msh(insns, BPF_MAXINSNS - 2);
> +}
> +
> +static int fill_jump_over_1821_msh(struct sock_filter *insns)
> +{
> + return fill_jump_over_msh(insns, 1821);
> +}
> +
> +static int fill_jump_over_1820_msh(struct sock_filter *insns)
> +{
> + return fill_jump_over_msh(insns, 1820);
> +}
> +
> +static int fill_empty(struct sock_filter *insns)
> +{
> + return 0;
> +}
> +
> +/* A conditional jump past 32K converted instructions */
> +static int fill_late_jump(struct sock_filter *insns)
> +{
> + int i;
> +
> + for (i = 0; i < 2000; i++)
> + insns[i] = STMT(BPF_LDX | BPF_B | BPF_MSH, 0);
> + insns[2000] = STMT(BPF_LD | BPF_IMM, 1);
> + insns[2001] = JUMP(BPF_JMP | BPF_JEQ | BPF_K, 1, 0, 1);
> + insns[2002] = STMT(BPF_RET | BPF_K, 1);
> + insns[2003] = STMT(BPF_RET | BPF_K, 2);
> + return 2004;
> +}
> +
> +/* A conditional jump over 254 loads, which expand to 12 instructions each */
> +static int fill_jump_over_ld_abs(struct sock_filter *insns, bool taken)
> +{
> + int i;
> +
> + insns[0] = STMT(BPF_LD | BPF_B | BPF_ABS, 0);
> + insns[1] = JUMP(BPF_JMP | BPF_JEQ | BPF_K, 1, taken ? 255 : 0, taken ? 0 : 255);
> + for (i = 2; i < 256; i++)
> + insns[i] = STMT(BPF_LD | BPF_B | BPF_ABS, 0);
> + insns[256] = STMT(BPF_RET | BPF_K, 2);
> + insns[257] = STMT(BPF_RET | BPF_K, 3);
> + return 258;
> +}
> +
> +static int fill_jt_over_ld_abs(struct sock_filter *insns)
> +{
> + return fill_jump_over_ld_abs(insns, true);
> +}
> +
> +static int fill_jf_over_ld_abs(struct sock_filter *insns)
> +{
> + return fill_jump_over_ld_abs(insns, false);
> +}
> +
> +/* The result is small enough for the kept length to show it as is */
> +static int fill_all_msh(struct sock_filter *insns)
> +{
> + int len = BPF_MAXINSNS, i;
> +
> + for (i = 0; i < len - 3; i++)
> + insns[i] = STMT(BPF_LDX | BPF_B | BPF_MSH, 0);
> + insns[len - 3] = STMT(BPF_LD | BPF_IMM, 0x2a);
> + insns[len - 2] = STMT(BPF_ALU | BPF_XOR | BPF_X, 0);
> + insns[len - 1] = STMT(BPF_RET | BPF_A, 0);
> + return len;
> +}
> +
> +static int fill_ld_abs_cpu(struct sock_filter *insns)
> +{
> + int len = BPF_MAXINSNS, i;
> +
> + for (i = 0; i < len - 1; i += 2) {
> + insns[i] = STMT(BPF_LD | BPF_B | BPF_ABS, 0);
> + insns[i + 1] = STMT(BPF_LD | BPF_W | BPF_ABS, SKF_AD_OFF + SKF_AD_CPU);
> + }
> + insns[len - 1] = STMT(BPF_RET | BPF_K, 0xbee);
> + return len;
> +}
> +
> +static const struct cbpf_case cases[] = {
> + {
> + .descr = "TAX",
> + .insns = INSNS(
> + BPF_STMT(BPF_LD | BPF_IMM, 1),
> + BPF_STMT(BPF_MISC | BPF_TAX, 0),
> + BPF_STMT(BPF_LD | BPF_IMM, 2),
> + BPF_STMT(BPF_ALU | BPF_ADD | BPF_X, 0),
> + BPF_STMT(BPF_ALU | BPF_NEG, 0), /* A == -3 */
> + BPF_STMT(BPF_MISC | BPF_TAX, 0),
> + BPF_STMT(BPF_LD | BPF_LEN, 0),
> + BPF_STMT(BPF_ALU | BPF_ADD | BPF_X, 0),
> + BPF_STMT(BPF_MISC | BPF_TAX, 0), /* X == len - 3 */
> + BPF_STMT(BPF_LD | BPF_B | BPF_IND, 1),
> + BPF_STMT(BPF_RET | BPF_A, 0)
> + ),
> + .data = DATA(10, 20, 30, 40, 50),
> + .runs = RUNS({ 2, 10 }, { 3, 20 }, { 4, 30 }),
> + },
> + {
> + .descr = "TXA",
> + .insns = INSNS(
> + BPF_STMT(BPF_LDX | BPF_LEN, 0),
> + BPF_STMT(BPF_MISC | BPF_TXA, 0),
> + BPF_STMT(BPF_ALU | BPF_ADD | BPF_X, 0),
> + BPF_STMT(BPF_RET | BPF_A, 0) /* A == len * 2 */
> + ),
> + .data = DATA(10, 20, 30, 40, 50),
> + .runs = RUNS({ 1, 2 }, { 3, 6 }, { 4, 8 }),
> + },
I don't like it. It just copies old test_lib.ko into test_progs.
I'd prefer to keep cbpf untested. It's not like anyone is working on it.
pw-bot: cr
next prev parent reply other threads:[~2026-10-15 19:08 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-15 17:19 [PATCH bpf-next 1/2] selftests/bpf: Follow-up coverage to JIT instruction tests Daniel Borkmann
2026-10-15 17:19 ` [PATCH bpf-next 2/2] selftests/bpf: Add JIT cBPF-related " Daniel Borkmann
2026-10-15 17:31 ` sashiko-bot
2026-10-15 18:08 ` bot+bpf-ci
2026-10-15 19:08 ` Alexei Starovoitov [this message]
2026-10-15 19:11 ` Daniel Borkmann
2026-10-15 18:08 ` [PATCH bpf-next 1/2] selftests/bpf: Follow-up coverage to JIT " bot+bpf-ci
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=DM5NCJVIY5GR.3KH59CI540I82@gmail.com \
--to=alexei.starovoitov@gmail.com \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=memxor@gmail.com \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox