Merge tag 'bpf-next-6.16' of git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next
Pull bpf updates from Alexei Starovoitov:
- Fix and improve BTF deduplication of identical BTF types (Alan
Maguire and Andrii Nakryiko)
- Support up to 12 arguments in BPF trampoline on arm64 (Xu Kuohai and
Alexis Lothoré)
- Support load-acquire and store-release instructions in BPF JIT on
riscv64 (Andrea Parri)
- Fix uninitialized values in BPF_{CORE,PROBE}_READ macros (Anton
Protopopov)
- Streamline allowed helpers across program types (Feng Yang)
- Support atomic update for hashtab of BPF maps (Hou Tao)
- Implement json output for BPF helpers (Ihor Solodrai)
- Several s390 JIT fixes (Ilya Leoshkevich)
- Various sockmap fixes (Jiayuan Chen)
- Support mmap of vmlinux BTF data (Lorenz Bauer)
- Support BPF rbtree traversal and list peeking (Martin KaFai Lau)
- Tests for sockmap/sockhash redirection (Michal Luczaj)
- Introduce kfuncs for memory reads into dynptrs (Mykyta Yatsenko)
- Add support for dma-buf iterators in BPF (T.J. Mercier)
- The verifier support for __bpf_trap() (Yonghong Song)
* tag 'bpf-next-6.16' of git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next: (135 commits)
bpf, arm64: Remove unused-but-set function and variable.
selftests/bpf: Add tests with stack ptr register in conditional jmp
bpf: Do not include stack ptr register in precision backtracking bookkeeping
selftests/bpf: enable many-args tests for arm64
bpf, arm64: Support up to 12 function arguments
bpf: Check rcu_read_lock_trace_held() in bpf_map_lookup_percpu_elem()
bpf: Avoid __bpf_prog_ret0_warn when jit fails
bpftool: Add support for custom BTF path in prog load/loadall
selftests/bpf: Add unit tests with __bpf_trap() kfunc
bpf: Warn with __bpf_trap() kfunc maybe due to uninitialized variable
bpf: Remove special_kfunc_set from verifier
selftests/bpf: Add test for open coded dmabuf_iter
selftests/bpf: Add test for dmabuf_iter
bpf: Add open coded dmabuf iterator
bpf: Add dmabuf iterator
dma-buf: Rename debugfs symbols
bpf: Fix error return value in bpf_copy_from_user_dynptr
libbpf: Use mmap to parse vmlinux BTF from sysfs
selftests: bpf: Add a test for mmapable vmlinux BTF
btf: Allow mmap of vmlinux btf
...
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
# TEMPORARY
|
||||
# Alphabetical order
|
||||
dynptr/test_probe_read_user_str_dynptr # disabled until https://patchwork.kernel.org/project/linux-mm/patch/20250422131449.57177-1-mykyta.yatsenko5@gmail.com/ makes it into the bpf-next
|
||||
get_stack_raw_tp # spams with kernel warnings until next bpf -> bpf-next merge
|
||||
stacktrace_build_id
|
||||
stacktrace_build_id_nmi
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
fentry_test/fentry_many_args # fentry_many_args:FAIL:fentry_many_args_attach unexpected error: -524
|
||||
fexit_test/fexit_many_args # fexit_many_args:FAIL:fexit_many_args_attach unexpected error: -524
|
||||
tracing_struct/struct_many_args # struct_many_args:FAIL:tracing_struct_many_args__attach unexpected error: -524
|
||||
|
||||
@@ -34,6 +34,9 @@ OPT_FLAGS ?= $(if $(RELEASE),-O2,-O0)
|
||||
LIBELF_CFLAGS := $(shell $(PKG_CONFIG) libelf --cflags 2>/dev/null)
|
||||
LIBELF_LIBS := $(shell $(PKG_CONFIG) libelf --libs 2>/dev/null || echo -lelf)
|
||||
|
||||
SKIP_DOCS ?=
|
||||
SKIP_LLVM ?=
|
||||
|
||||
ifeq ($(srctree),)
|
||||
srctree := $(patsubst %/,%,$(dir $(CURDIR)))
|
||||
srctree := $(patsubst %/,%,$(dir $(srctree)))
|
||||
@@ -172,6 +175,7 @@ override OUTPUT := $(patsubst %/,%,$(OUTPUT))
|
||||
endif
|
||||
endif
|
||||
|
||||
ifneq ($(SKIP_LLVM),1)
|
||||
ifeq ($(feature-llvm),1)
|
||||
LLVM_CFLAGS += -DHAVE_LLVM_SUPPORT
|
||||
LLVM_CONFIG_LIB_COMPONENTS := mcdisassembler all-targets
|
||||
@@ -180,13 +184,14 @@ ifeq ($(feature-llvm),1)
|
||||
# Prefer linking statically if it's available, otherwise fallback to shared
|
||||
ifeq ($(shell $(LLVM_CONFIG) --link-static --libs >/dev/null 2>&1 && echo static),static)
|
||||
LLVM_LDLIBS += $(shell $(LLVM_CONFIG) --link-static --libs $(LLVM_CONFIG_LIB_COMPONENTS))
|
||||
LLVM_LDLIBS += $(shell $(LLVM_CONFIG) --link-static --system-libs $(LLVM_CONFIG_LIB_COMPONENTS))
|
||||
LLVM_LDLIBS += $(filter-out -lxml2,$(shell $(LLVM_CONFIG) --link-static --system-libs $(LLVM_CONFIG_LIB_COMPONENTS)))
|
||||
LLVM_LDLIBS += -lstdc++
|
||||
else
|
||||
LLVM_LDLIBS += $(shell $(LLVM_CONFIG) --link-shared --libs $(LLVM_CONFIG_LIB_COMPONENTS))
|
||||
endif
|
||||
LLVM_LDFLAGS += $(shell $(LLVM_CONFIG) --ldflags)
|
||||
endif
|
||||
endif
|
||||
|
||||
SCRATCH_DIR := $(OUTPUT)/tools
|
||||
BUILD_DIR := $(SCRATCH_DIR)/build
|
||||
@@ -358,7 +363,9 @@ $(CROSS_BPFTOOL): $(wildcard $(BPFTOOLDIR)/*.[ch] $(BPFTOOLDIR)/Makefile) \
|
||||
prefix= DESTDIR=$(SCRATCH_DIR)/ install-bin
|
||||
endif
|
||||
|
||||
ifneq ($(SKIP_DOCS),1)
|
||||
all: docs
|
||||
endif
|
||||
|
||||
docs:
|
||||
$(Q)RST2MAN_OPTS="--exit-status=1" $(MAKE) $(submake_extras) \
|
||||
@@ -673,9 +680,6 @@ ifneq ($2:$(OUTPUT),:$(shell pwd))
|
||||
$(Q)rsync -aq $$^ $(TRUNNER_OUTPUT)/
|
||||
endif
|
||||
|
||||
$(OUTPUT)/$(TRUNNER_BINARY): LDLIBS += $$(LLVM_LDLIBS)
|
||||
$(OUTPUT)/$(TRUNNER_BINARY): LDFLAGS += $$(LLVM_LDFLAGS)
|
||||
|
||||
# some X.test.o files have runtime dependencies on Y.bpf.o files
|
||||
$(OUTPUT)/$(TRUNNER_BINARY): | $(TRUNNER_BPF_OBJS)
|
||||
|
||||
@@ -686,7 +690,7 @@ $(OUTPUT)/$(TRUNNER_BINARY): $(TRUNNER_TEST_OBJS) \
|
||||
$(OUTPUT)/veristat \
|
||||
| $(TRUNNER_BINARY)-extras
|
||||
$$(call msg,BINARY,,$$@)
|
||||
$(Q)$$(CC) $$(CFLAGS) $$(filter %.a %.o,$$^) $$(LDLIBS) $$(LDFLAGS) -o $$@
|
||||
$(Q)$$(CC) $$(CFLAGS) $$(filter %.a %.o,$$^) $$(LDLIBS) $$(LLVM_LDLIBS) $$(LDFLAGS) $$(LLVM_LDFLAGS) -o $$@
|
||||
$(Q)$(RESOLVE_BTFIDS) --btf $(TRUNNER_OUTPUT)/btf_data.bpf.o $$@
|
||||
$(Q)ln -sf $(if $2,..,.)/tools/build/bpftool/$(USE_BOOTSTRAP)bpftool \
|
||||
$(OUTPUT)/$(if $2,$2/)bpftool
|
||||
@@ -811,6 +815,7 @@ $(OUTPUT)/bench_local_storage_create.o: $(OUTPUT)/bench_local_storage_create.ske
|
||||
$(OUTPUT)/bench_bpf_hashmap_lookup.o: $(OUTPUT)/bpf_hashmap_lookup.skel.h
|
||||
$(OUTPUT)/bench_htab_mem.o: $(OUTPUT)/htab_mem_bench.skel.h
|
||||
$(OUTPUT)/bench_bpf_crypto.o: $(OUTPUT)/crypto_bench.skel.h
|
||||
$(OUTPUT)/bench_sockmap.o: $(OUTPUT)/bench_sockmap_prog.skel.h
|
||||
$(OUTPUT)/bench.o: bench.h testing_helpers.h $(BPFOBJ)
|
||||
$(OUTPUT)/bench: LDLIBS += -lm
|
||||
$(OUTPUT)/bench: $(OUTPUT)/bench.o \
|
||||
@@ -831,6 +836,7 @@ $(OUTPUT)/bench: $(OUTPUT)/bench.o \
|
||||
$(OUTPUT)/bench_local_storage_create.o \
|
||||
$(OUTPUT)/bench_htab_mem.o \
|
||||
$(OUTPUT)/bench_bpf_crypto.o \
|
||||
$(OUTPUT)/bench_sockmap.o \
|
||||
#
|
||||
$(call msg,BINARY,,$@)
|
||||
$(Q)$(CC) $(CFLAGS) $(LDFLAGS) $(filter %.a %.o,$^) $(LDLIBS) -o $@
|
||||
|
||||
@@ -283,6 +283,7 @@ extern struct argp bench_local_storage_create_argp;
|
||||
extern struct argp bench_htab_mem_argp;
|
||||
extern struct argp bench_trigger_batch_argp;
|
||||
extern struct argp bench_crypto_argp;
|
||||
extern struct argp bench_sockmap_argp;
|
||||
|
||||
static const struct argp_child bench_parsers[] = {
|
||||
{ &bench_ringbufs_argp, 0, "Ring buffers benchmark", 0 },
|
||||
@@ -297,6 +298,7 @@ static const struct argp_child bench_parsers[] = {
|
||||
{ &bench_htab_mem_argp, 0, "hash map memory benchmark", 0 },
|
||||
{ &bench_trigger_batch_argp, 0, "BPF triggering benchmark", 0 },
|
||||
{ &bench_crypto_argp, 0, "bpf crypto benchmark", 0 },
|
||||
{ &bench_sockmap_argp, 0, "bpf sockmap benchmark", 0 },
|
||||
{},
|
||||
};
|
||||
|
||||
@@ -555,6 +557,7 @@ extern const struct bench bench_local_storage_create;
|
||||
extern const struct bench bench_htab_mem;
|
||||
extern const struct bench bench_crypto_encrypt;
|
||||
extern const struct bench bench_crypto_decrypt;
|
||||
extern const struct bench bench_sockmap;
|
||||
|
||||
static const struct bench *benchs[] = {
|
||||
&bench_count_global,
|
||||
@@ -621,6 +624,7 @@ static const struct bench *benchs[] = {
|
||||
&bench_htab_mem,
|
||||
&bench_crypto_encrypt,
|
||||
&bench_crypto_decrypt,
|
||||
&bench_sockmap,
|
||||
};
|
||||
|
||||
static void find_benchmark(void)
|
||||
|
||||
@@ -279,6 +279,7 @@ static void htab_mem_read_mem_cgrp_file(const char *name, unsigned long *value)
|
||||
}
|
||||
|
||||
got = read(fd, buf, sizeof(buf) - 1);
|
||||
close(fd);
|
||||
if (got <= 0) {
|
||||
*value = 0;
|
||||
return;
|
||||
@@ -286,8 +287,6 @@ static void htab_mem_read_mem_cgrp_file(const char *name, unsigned long *value)
|
||||
buf[got] = 0;
|
||||
|
||||
*value = strtoull(buf, NULL, 0);
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void htab_mem_measure(struct bench_res *res)
|
||||
|
||||
@@ -0,0 +1,598 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include <error.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/sendfile.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
#include <argp.h>
|
||||
#include "bench.h"
|
||||
#include "bench_sockmap_prog.skel.h"
|
||||
|
||||
#define FILE_SIZE (128 * 1024)
|
||||
#define DATA_REPEAT_SIZE 10
|
||||
|
||||
static const char snd_data[DATA_REPEAT_SIZE] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
||||
|
||||
/* c1 <-> [p1, p2] <-> c2
|
||||
* RX bench(BPF_SK_SKB_STREAM_VERDICT):
|
||||
* ARG_FW_RX_PASS:
|
||||
* send(p2) -> recv(c2) -> bpf skb passthrough -> recv(c2)
|
||||
* ARG_FW_RX_VERDICT_EGRESS:
|
||||
* send(c1) -> verdict skb to tx queuec of p2 -> recv(c2)
|
||||
* ARG_FW_RX_VERDICT_INGRESS:
|
||||
* send(c1) -> verdict skb to rx queuec of c2 -> recv(c2)
|
||||
*
|
||||
* TX bench(BPF_SK_MSG_VERDIC):
|
||||
* ARG_FW_TX_PASS:
|
||||
* send(p2) -> bpf msg passthrough -> send(p2) -> recv(c2)
|
||||
* ARG_FW_TX_VERDICT_INGRESS:
|
||||
* send(p2) -> verdict msg to rx queue of c2 -> recv(c2)
|
||||
* ARG_FW_TX_VERDICT_EGRESS:
|
||||
* send(p1) -> verdict msg to tx queue of p2 -> recv(c2)
|
||||
*/
|
||||
enum SOCKMAP_ARG_FLAG {
|
||||
ARG_FW_RX_NORMAL = 11000,
|
||||
ARG_FW_RX_PASS,
|
||||
ARG_FW_RX_VERDICT_EGRESS,
|
||||
ARG_FW_RX_VERDICT_INGRESS,
|
||||
ARG_FW_TX_NORMAL,
|
||||
ARG_FW_TX_PASS,
|
||||
ARG_FW_TX_VERDICT_INGRESS,
|
||||
ARG_FW_TX_VERDICT_EGRESS,
|
||||
ARG_CTL_RX_STRP,
|
||||
ARG_CONSUMER_DELAY_TIME,
|
||||
ARG_PRODUCER_DURATION,
|
||||
};
|
||||
|
||||
#define TXMODE_NORMAL() \
|
||||
((ctx.mode) == ARG_FW_TX_NORMAL)
|
||||
|
||||
#define TXMODE_BPF_INGRESS() \
|
||||
((ctx.mode) == ARG_FW_TX_VERDICT_INGRESS)
|
||||
|
||||
#define TXMODE_BPF_EGRESS() \
|
||||
((ctx.mode) == ARG_FW_TX_VERDICT_EGRESS)
|
||||
|
||||
#define TXMODE_BPF_PASS() \
|
||||
((ctx.mode) == ARG_FW_TX_PASS)
|
||||
|
||||
#define TXMODE_BPF() ( \
|
||||
TXMODE_BPF_PASS() || \
|
||||
TXMODE_BPF_INGRESS() || \
|
||||
TXMODE_BPF_EGRESS())
|
||||
|
||||
#define TXMODE() ( \
|
||||
TXMODE_NORMAL() || \
|
||||
TXMODE_BPF())
|
||||
|
||||
#define RXMODE_NORMAL() \
|
||||
((ctx.mode) == ARG_FW_RX_NORMAL)
|
||||
|
||||
#define RXMODE_BPF_PASS() \
|
||||
((ctx.mode) == ARG_FW_RX_PASS)
|
||||
|
||||
#define RXMODE_BPF_VERDICT_EGRESS() \
|
||||
((ctx.mode) == ARG_FW_RX_VERDICT_EGRESS)
|
||||
|
||||
#define RXMODE_BPF_VERDICT_INGRESS() \
|
||||
((ctx.mode) == ARG_FW_RX_VERDICT_INGRESS)
|
||||
|
||||
#define RXMODE_BPF_VERDICT() ( \
|
||||
RXMODE_BPF_VERDICT_INGRESS() || \
|
||||
RXMODE_BPF_VERDICT_EGRESS())
|
||||
|
||||
#define RXMODE_BPF() ( \
|
||||
RXMODE_BPF_PASS() || \
|
||||
RXMODE_BPF_VERDICT())
|
||||
|
||||
#define RXMODE() ( \
|
||||
RXMODE_NORMAL() || \
|
||||
RXMODE_BPF())
|
||||
|
||||
static struct socmap_ctx {
|
||||
struct bench_sockmap_prog *skel;
|
||||
enum SOCKMAP_ARG_FLAG mode;
|
||||
#define c1 fds[0]
|
||||
#define p1 fds[1]
|
||||
#define c2 fds[2]
|
||||
#define p2 fds[3]
|
||||
#define sfd fds[4]
|
||||
int fds[5];
|
||||
long send_calls;
|
||||
long read_calls;
|
||||
long prod_send;
|
||||
long user_read;
|
||||
int file_size;
|
||||
int delay_consumer;
|
||||
int prod_run_time;
|
||||
int strp_size;
|
||||
} ctx = {
|
||||
.prod_send = 0,
|
||||
.user_read = 0,
|
||||
.file_size = FILE_SIZE,
|
||||
.mode = ARG_FW_RX_VERDICT_EGRESS,
|
||||
.fds = {0},
|
||||
.delay_consumer = 0,
|
||||
.prod_run_time = 0,
|
||||
.strp_size = 0,
|
||||
};
|
||||
|
||||
static void bench_sockmap_prog_destroy(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < sizeof(ctx.fds); i++) {
|
||||
if (ctx.fds[0] > 0)
|
||||
close(ctx.fds[i]);
|
||||
}
|
||||
|
||||
bench_sockmap_prog__destroy(ctx.skel);
|
||||
}
|
||||
|
||||
static void init_addr(struct sockaddr_storage *ss,
|
||||
socklen_t *len)
|
||||
{
|
||||
struct sockaddr_in *addr4 = memset(ss, 0, sizeof(*ss));
|
||||
|
||||
addr4->sin_family = AF_INET;
|
||||
addr4->sin_port = 0;
|
||||
addr4->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
*len = sizeof(*addr4);
|
||||
}
|
||||
|
||||
static bool set_non_block(int fd, bool blocking)
|
||||
{
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
|
||||
if (flags == -1)
|
||||
return false;
|
||||
flags = blocking ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK);
|
||||
return (fcntl(fd, F_SETFL, flags) == 0);
|
||||
}
|
||||
|
||||
static int create_pair(int *c, int *p, int type)
|
||||
{
|
||||
struct sockaddr_storage addr;
|
||||
int err, cfd, pfd;
|
||||
socklen_t addr_len = sizeof(struct sockaddr_storage);
|
||||
|
||||
err = getsockname(ctx.sfd, (struct sockaddr *)&addr, &addr_len);
|
||||
if (err) {
|
||||
fprintf(stderr, "getsockname error %d\n", errno);
|
||||
return err;
|
||||
}
|
||||
cfd = socket(AF_INET, type, 0);
|
||||
if (cfd < 0) {
|
||||
fprintf(stderr, "socket error %d\n", errno);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = connect(cfd, (struct sockaddr *)&addr, addr_len);
|
||||
if (err && errno != EINPROGRESS) {
|
||||
fprintf(stderr, "connect error %d\n", errno);
|
||||
return err;
|
||||
}
|
||||
|
||||
pfd = accept(ctx.sfd, NULL, NULL);
|
||||
if (pfd < 0) {
|
||||
fprintf(stderr, "accept error %d\n", errno);
|
||||
return err;
|
||||
}
|
||||
*c = cfd;
|
||||
*p = pfd;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int create_sockets(void)
|
||||
{
|
||||
struct sockaddr_storage addr;
|
||||
int err, one = 1;
|
||||
socklen_t addr_len;
|
||||
|
||||
init_addr(&addr, &addr_len);
|
||||
ctx.sfd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (ctx.sfd < 0) {
|
||||
fprintf(stderr, "socket error:%d\n", errno);
|
||||
return ctx.sfd;
|
||||
}
|
||||
err = setsockopt(ctx.sfd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one));
|
||||
if (err) {
|
||||
fprintf(stderr, "setsockopt error:%d\n", errno);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = bind(ctx.sfd, (struct sockaddr *)&addr, addr_len);
|
||||
if (err) {
|
||||
fprintf(stderr, "bind error:%d\n", errno);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = listen(ctx.sfd, SOMAXCONN);
|
||||
if (err) {
|
||||
fprintf(stderr, "listen error:%d\n", errno);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = create_pair(&ctx.c1, &ctx.p1, SOCK_STREAM);
|
||||
if (err) {
|
||||
fprintf(stderr, "create_pair 1 error\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
err = create_pair(&ctx.c2, &ctx.p2, SOCK_STREAM);
|
||||
if (err) {
|
||||
fprintf(stderr, "create_pair 2 error\n");
|
||||
return err;
|
||||
}
|
||||
printf("create socket fd c1:%d p1:%d c2:%d p2:%d\n",
|
||||
ctx.c1, ctx.p1, ctx.c2, ctx.p2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void validate(void)
|
||||
{
|
||||
if (env.consumer_cnt != 2 || env.producer_cnt != 1 ||
|
||||
!env.affinity)
|
||||
goto err;
|
||||
return;
|
||||
err:
|
||||
fprintf(stderr, "argument '-c 2 -p 1 -a' is necessary");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static int setup_rx_sockmap(void)
|
||||
{
|
||||
int verdict, pass, parser, map;
|
||||
int zero = 0, one = 1;
|
||||
int err;
|
||||
|
||||
parser = bpf_program__fd(ctx.skel->progs.prog_skb_parser);
|
||||
verdict = bpf_program__fd(ctx.skel->progs.prog_skb_verdict);
|
||||
pass = bpf_program__fd(ctx.skel->progs.prog_skb_pass);
|
||||
map = bpf_map__fd(ctx.skel->maps.sock_map_rx);
|
||||
|
||||
if (ctx.strp_size != 0) {
|
||||
ctx.skel->bss->pkt_size = ctx.strp_size;
|
||||
err = bpf_prog_attach(parser, map, BPF_SK_SKB_STREAM_PARSER, 0);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
if (RXMODE_BPF_VERDICT())
|
||||
err = bpf_prog_attach(verdict, map, BPF_SK_SKB_STREAM_VERDICT, 0);
|
||||
else if (RXMODE_BPF_PASS())
|
||||
err = bpf_prog_attach(pass, map, BPF_SK_SKB_STREAM_VERDICT, 0);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (RXMODE_BPF_PASS())
|
||||
return bpf_map_update_elem(map, &zero, &ctx.c2, BPF_NOEXIST);
|
||||
|
||||
err = bpf_map_update_elem(map, &zero, &ctx.p1, BPF_NOEXIST);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (RXMODE_BPF_VERDICT_INGRESS()) {
|
||||
ctx.skel->bss->verdict_dir = BPF_F_INGRESS;
|
||||
err = bpf_map_update_elem(map, &one, &ctx.c2, BPF_NOEXIST);
|
||||
} else {
|
||||
err = bpf_map_update_elem(map, &one, &ctx.p2, BPF_NOEXIST);
|
||||
}
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int setup_tx_sockmap(void)
|
||||
{
|
||||
int zero = 0, one = 1;
|
||||
int prog, map;
|
||||
int err;
|
||||
|
||||
map = bpf_map__fd(ctx.skel->maps.sock_map_tx);
|
||||
prog = TXMODE_BPF_PASS() ?
|
||||
bpf_program__fd(ctx.skel->progs.prog_skmsg_pass) :
|
||||
bpf_program__fd(ctx.skel->progs.prog_skmsg_verdict);
|
||||
|
||||
err = bpf_prog_attach(prog, map, BPF_SK_MSG_VERDICT, 0);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (TXMODE_BPF_EGRESS()) {
|
||||
err = bpf_map_update_elem(map, &zero, &ctx.p1, BPF_NOEXIST);
|
||||
err |= bpf_map_update_elem(map, &one, &ctx.p2, BPF_NOEXIST);
|
||||
} else {
|
||||
ctx.skel->bss->verdict_dir = BPF_F_INGRESS;
|
||||
err = bpf_map_update_elem(map, &zero, &ctx.p2, BPF_NOEXIST);
|
||||
err |= bpf_map_update_elem(map, &one, &ctx.c2, BPF_NOEXIST);
|
||||
}
|
||||
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void setup(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
ctx.skel = bench_sockmap_prog__open_and_load();
|
||||
if (!ctx.skel) {
|
||||
fprintf(stderr, "error loading skel\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (create_sockets()) {
|
||||
fprintf(stderr, "create_net_mode error\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (RXMODE_BPF()) {
|
||||
err = setup_rx_sockmap();
|
||||
if (err) {
|
||||
fprintf(stderr, "setup_rx_sockmap error:%d\n", err);
|
||||
goto err;
|
||||
}
|
||||
} else if (TXMODE_BPF()) {
|
||||
err = setup_tx_sockmap();
|
||||
if (err) {
|
||||
fprintf(stderr, "setup_tx_sockmap error:%d\n", err);
|
||||
goto err;
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "unknown sockmap bench mode: %d\n", ctx.mode);
|
||||
goto err;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
err:
|
||||
bench_sockmap_prog_destroy();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void measure(struct bench_res *res)
|
||||
{
|
||||
res->drops = atomic_swap(&ctx.prod_send, 0);
|
||||
res->hits = atomic_swap(&ctx.skel->bss->process_byte, 0);
|
||||
res->false_hits = atomic_swap(&ctx.user_read, 0);
|
||||
res->important_hits = atomic_swap(&ctx.send_calls, 0);
|
||||
res->important_hits |= atomic_swap(&ctx.read_calls, 0) << 32;
|
||||
}
|
||||
|
||||
static void verify_data(int *check_pos, char *buf, int rcv)
|
||||
{
|
||||
for (int i = 0 ; i < rcv; i++) {
|
||||
if (buf[i] != snd_data[(*check_pos) % DATA_REPEAT_SIZE]) {
|
||||
fprintf(stderr, "verify data fail");
|
||||
exit(1);
|
||||
}
|
||||
(*check_pos)++;
|
||||
if (*check_pos >= FILE_SIZE)
|
||||
*check_pos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void *consumer(void *input)
|
||||
{
|
||||
int rcv, sent;
|
||||
int check_pos = 0;
|
||||
int tid = (long)input;
|
||||
int recv_buf_size = FILE_SIZE;
|
||||
char *buf = malloc(recv_buf_size);
|
||||
int delay_read = ctx.delay_consumer;
|
||||
|
||||
if (!buf) {
|
||||
fprintf(stderr, "fail to init read buffer");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (tid == 1) {
|
||||
/* consumer 1 is unused for tx test and stream verdict test */
|
||||
if (RXMODE_BPF() || TXMODE())
|
||||
return NULL;
|
||||
/* it's only for RX_NORMAL which service as reserve-proxy mode */
|
||||
rcv = read(ctx.p1, buf, recv_buf_size);
|
||||
if (rcv < 0) {
|
||||
fprintf(stderr, "fail to read p1");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sent = send(ctx.p2, buf, recv_buf_size, 0);
|
||||
if (sent < 0) {
|
||||
fprintf(stderr, "fail to send p2");
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
if (delay_read != 0) {
|
||||
if (delay_read < 0)
|
||||
return NULL;
|
||||
sleep(delay_read);
|
||||
delay_read = 0;
|
||||
}
|
||||
/* read real endpoint by consumer 0 */
|
||||
atomic_inc(&ctx.read_calls);
|
||||
rcv = read(ctx.c2, buf, recv_buf_size);
|
||||
if (rcv < 0 && errno != EAGAIN) {
|
||||
fprintf(stderr, "%s fail to read c2 %d\n", __func__, errno);
|
||||
return NULL;
|
||||
}
|
||||
verify_data(&check_pos, buf, rcv);
|
||||
atomic_add(&ctx.user_read, rcv);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *producer(void *input)
|
||||
{
|
||||
int off = 0, fp, need_sent, sent;
|
||||
int file_size = ctx.file_size;
|
||||
struct timespec ts1, ts2;
|
||||
int target;
|
||||
FILE *file;
|
||||
|
||||
file = tmpfile();
|
||||
if (!file) {
|
||||
fprintf(stderr, "create file for sendfile");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* we need simple verify */
|
||||
for (int i = 0; i < file_size; i++) {
|
||||
if (fwrite(&snd_data[off], sizeof(char), 1, file) != 1) {
|
||||
fprintf(stderr, "init tmpfile error");
|
||||
return NULL;
|
||||
}
|
||||
if (++off >= sizeof(snd_data))
|
||||
off = 0;
|
||||
}
|
||||
fflush(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
|
||||
fp = fileno(file);
|
||||
need_sent = file_size;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts1);
|
||||
|
||||
if (RXMODE_BPF_VERDICT())
|
||||
target = ctx.c1;
|
||||
else if (TXMODE_BPF_EGRESS())
|
||||
target = ctx.p1;
|
||||
else
|
||||
target = ctx.p2;
|
||||
set_non_block(target, true);
|
||||
while (true) {
|
||||
if (ctx.prod_run_time) {
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts2);
|
||||
if (ts2.tv_sec - ts1.tv_sec > ctx.prod_run_time)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
atomic_inc(&ctx.send_calls);
|
||||
sent = sendfile(target, fp, NULL, need_sent);
|
||||
if (sent < 0) {
|
||||
if (errno != EAGAIN && errno != ENOMEM && errno != ENOBUFS) {
|
||||
fprintf(stderr, "sendfile return %d, errorno %d:%s\n",
|
||||
sent, errno, strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
continue;
|
||||
} else if (sent < need_sent) {
|
||||
need_sent -= sent;
|
||||
atomic_add(&ctx.prod_send, sent);
|
||||
continue;
|
||||
}
|
||||
atomic_add(&ctx.prod_send, need_sent);
|
||||
need_sent = file_size;
|
||||
lseek(fp, 0, SEEK_SET);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void report_progress(int iter, struct bench_res *res, long delta_ns)
|
||||
{
|
||||
double speed_mbs, prod_mbs, bpf_mbs, send_hz, read_hz;
|
||||
|
||||
prod_mbs = res->drops / 1000000.0 / (delta_ns / 1000000000.0);
|
||||
speed_mbs = res->false_hits / 1000000.0 / (delta_ns / 1000000000.0);
|
||||
bpf_mbs = res->hits / 1000000.0 / (delta_ns / 1000000000.0);
|
||||
send_hz = (res->important_hits & 0xFFFFFFFF) / (delta_ns / 1000000000.0);
|
||||
read_hz = (res->important_hits >> 32) / (delta_ns / 1000000000.0);
|
||||
|
||||
printf("Iter %3d (%7.3lfus): ",
|
||||
iter, (delta_ns - 1000000000) / 1000.0);
|
||||
printf("Send Speed %8.3lf MB/s (%8.3lf calls/s), BPF Speed %8.3lf MB/s, "
|
||||
"Rcv Speed %8.3lf MB/s (%8.3lf calls/s)\n",
|
||||
prod_mbs, send_hz, bpf_mbs, speed_mbs, read_hz);
|
||||
}
|
||||
|
||||
static void report_final(struct bench_res res[], int res_cnt)
|
||||
{
|
||||
double verdict_mbs_mean = 0.0;
|
||||
long verdict_total = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < res_cnt; i++) {
|
||||
verdict_mbs_mean += res[i].hits / 1000000.0 / (0.0 + res_cnt);
|
||||
verdict_total += res[i].hits / 1000000.0;
|
||||
}
|
||||
|
||||
printf("Summary: total trans %8.3lu MB \u00B1 %5.3lf MB/s\n",
|
||||
verdict_total, verdict_mbs_mean);
|
||||
}
|
||||
|
||||
static const struct argp_option opts[] = {
|
||||
{ "rx-normal", ARG_FW_RX_NORMAL, NULL, 0,
|
||||
"simple reserve-proxy mode, no bfp enabled"},
|
||||
{ "rx-pass", ARG_FW_RX_PASS, NULL, 0,
|
||||
"run bpf prog but no redir applied"},
|
||||
{ "rx-strp", ARG_CTL_RX_STRP, "Byte", 0,
|
||||
"enable strparser and set the encapsulation size"},
|
||||
{ "rx-verdict-egress", ARG_FW_RX_VERDICT_EGRESS, NULL, 0,
|
||||
"forward data with bpf(stream verdict)"},
|
||||
{ "rx-verdict-ingress", ARG_FW_RX_VERDICT_INGRESS, NULL, 0,
|
||||
"forward data with bpf(stream verdict)"},
|
||||
{ "tx-normal", ARG_FW_TX_NORMAL, NULL, 0,
|
||||
"simple c-s mode, no bfp enabled"},
|
||||
{ "tx-pass", ARG_FW_TX_PASS, NULL, 0,
|
||||
"run bpf prog but no redir applied"},
|
||||
{ "tx-verdict-ingress", ARG_FW_TX_VERDICT_INGRESS, NULL, 0,
|
||||
"forward msg to ingress queue of another socket"},
|
||||
{ "tx-verdict-egress", ARG_FW_TX_VERDICT_EGRESS, NULL, 0,
|
||||
"forward msg to egress queue of another socket"},
|
||||
{ "delay-consumer", ARG_CONSUMER_DELAY_TIME, "SEC", 0,
|
||||
"delay consumer start"},
|
||||
{ "producer-duration", ARG_PRODUCER_DURATION, "SEC", 0,
|
||||
"producer duration"},
|
||||
{},
|
||||
};
|
||||
|
||||
static error_t parse_arg(int key, char *arg, struct argp_state *state)
|
||||
{
|
||||
switch (key) {
|
||||
case ARG_FW_RX_NORMAL...ARG_FW_TX_VERDICT_EGRESS:
|
||||
ctx.mode = key;
|
||||
break;
|
||||
case ARG_CONSUMER_DELAY_TIME:
|
||||
ctx.delay_consumer = strtol(arg, NULL, 10);
|
||||
break;
|
||||
case ARG_PRODUCER_DURATION:
|
||||
ctx.prod_run_time = strtol(arg, NULL, 10);
|
||||
break;
|
||||
case ARG_CTL_RX_STRP:
|
||||
ctx.strp_size = strtol(arg, NULL, 10);
|
||||
break;
|
||||
default:
|
||||
return ARGP_ERR_UNKNOWN;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* exported into benchmark runner */
|
||||
const struct argp bench_sockmap_argp = {
|
||||
.options = opts,
|
||||
.parser = parse_arg,
|
||||
};
|
||||
|
||||
/* Benchmark performance of creating bpf local storage */
|
||||
const struct bench bench_sockmap = {
|
||||
.name = "sockmap",
|
||||
.argp = &bench_sockmap_argp,
|
||||
.validate = validate,
|
||||
.setup = setup,
|
||||
.producer_thread = producer,
|
||||
.consumer_thread = consumer,
|
||||
.measure = measure,
|
||||
.report_progress = report_progress,
|
||||
.report_final = report_final,
|
||||
};
|
||||
@@ -591,4 +591,9 @@ extern int bpf_iter_kmem_cache_new(struct bpf_iter_kmem_cache *it) __weak __ksym
|
||||
extern struct kmem_cache *bpf_iter_kmem_cache_next(struct bpf_iter_kmem_cache *it) __weak __ksym;
|
||||
extern void bpf_iter_kmem_cache_destroy(struct bpf_iter_kmem_cache *it) __weak __ksym;
|
||||
|
||||
struct bpf_iter_dmabuf;
|
||||
extern int bpf_iter_dmabuf_new(struct bpf_iter_dmabuf *it) __weak __ksym;
|
||||
extern struct dma_buf *bpf_iter_dmabuf_next(struct bpf_iter_dmabuf *it) __weak __ksym;
|
||||
extern void bpf_iter_dmabuf_destroy(struct bpf_iter_dmabuf *it) __weak __ksym;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -22,6 +22,8 @@ CONFIG_CRYPTO_AES=y
|
||||
CONFIG_DEBUG_INFO=y
|
||||
CONFIG_DEBUG_INFO_BTF=y
|
||||
CONFIG_DEBUG_INFO_DWARF4=y
|
||||
CONFIG_DMABUF_HEAPS=y
|
||||
CONFIG_DMABUF_HEAPS_SYSTEM=y
|
||||
CONFIG_DUMMY=y
|
||||
CONFIG_DYNAMIC_FTRACE=y
|
||||
CONFIG_FPROBE=y
|
||||
@@ -108,6 +110,7 @@ CONFIG_SECURITY=y
|
||||
CONFIG_SECURITYFS=y
|
||||
CONFIG_SYN_COOKIES=y
|
||||
CONFIG_TEST_BPF=m
|
||||
CONFIG_UDMABUF=y
|
||||
CONFIG_USERFAULTFD=y
|
||||
CONFIG_VSOCKETS=y
|
||||
CONFIG_VXLAN=y
|
||||
|
||||
@@ -51,9 +51,11 @@ static void test_arena_spin_lock_size(int size)
|
||||
struct arena_spin_lock *skel;
|
||||
pthread_t thread_id[16];
|
||||
int prog_fd, i, err;
|
||||
int nthreads;
|
||||
void *ret;
|
||||
|
||||
if (get_nprocs() < 2) {
|
||||
nthreads = MIN(get_nprocs(), ARRAY_SIZE(thread_id));
|
||||
if (nthreads < 2) {
|
||||
test__skip();
|
||||
return;
|
||||
}
|
||||
@@ -66,25 +68,25 @@ static void test_arena_spin_lock_size(int size)
|
||||
goto end;
|
||||
}
|
||||
skel->bss->cs_count = size;
|
||||
skel->bss->limit = repeat * 16;
|
||||
skel->bss->limit = repeat * nthreads;
|
||||
|
||||
ASSERT_OK(pthread_barrier_init(&barrier, NULL, 16), "barrier init");
|
||||
ASSERT_OK(pthread_barrier_init(&barrier, NULL, nthreads), "barrier init");
|
||||
|
||||
prog_fd = bpf_program__fd(skel->progs.prog);
|
||||
for (i = 0; i < 16; i++) {
|
||||
for (i = 0; i < nthreads; i++) {
|
||||
err = pthread_create(&thread_id[i], NULL, &spin_lock_thread, &prog_fd);
|
||||
if (!ASSERT_OK(err, "pthread_create"))
|
||||
goto end_barrier;
|
||||
}
|
||||
|
||||
for (i = 0; i < 16; i++) {
|
||||
for (i = 0; i < nthreads; i++) {
|
||||
if (!ASSERT_OK(pthread_join(thread_id[i], &ret), "pthread_join"))
|
||||
goto end_barrier;
|
||||
if (!ASSERT_EQ(ret, &prog_fd, "ret == prog_fd"))
|
||||
goto end_barrier;
|
||||
}
|
||||
|
||||
ASSERT_EQ(skel->bss->counter, repeat * 16, "check counter value");
|
||||
ASSERT_EQ(skel->bss->counter, repeat * nthreads, "check counter value");
|
||||
|
||||
end_barrier:
|
||||
pthread_barrier_destroy(&barrier);
|
||||
|
||||
@@ -122,6 +122,85 @@ cleanup:
|
||||
test_attach_probe_manual__destroy(skel);
|
||||
}
|
||||
|
||||
/* attach uprobe/uretprobe long event name testings */
|
||||
static void test_attach_uprobe_long_event_name(void)
|
||||
{
|
||||
DECLARE_LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts);
|
||||
struct bpf_link *uprobe_link, *uretprobe_link;
|
||||
struct test_attach_probe_manual *skel;
|
||||
ssize_t uprobe_offset;
|
||||
char path[PATH_MAX] = {0};
|
||||
|
||||
skel = test_attach_probe_manual__open_and_load();
|
||||
if (!ASSERT_OK_PTR(skel, "skel_kprobe_manual_open_and_load"))
|
||||
return;
|
||||
|
||||
uprobe_offset = get_uprobe_offset(&trigger_func);
|
||||
if (!ASSERT_GE(uprobe_offset, 0, "uprobe_offset"))
|
||||
goto cleanup;
|
||||
|
||||
if (!ASSERT_GT(readlink("/proc/self/exe", path, PATH_MAX - 1), 0, "readlink"))
|
||||
goto cleanup;
|
||||
|
||||
/* manual-attach uprobe/uretprobe */
|
||||
uprobe_opts.attach_mode = PROBE_ATTACH_MODE_LEGACY;
|
||||
uprobe_opts.ref_ctr_offset = 0;
|
||||
uprobe_opts.retprobe = false;
|
||||
uprobe_link = bpf_program__attach_uprobe_opts(skel->progs.handle_uprobe,
|
||||
0 /* self pid */,
|
||||
path,
|
||||
uprobe_offset,
|
||||
&uprobe_opts);
|
||||
if (!ASSERT_OK_PTR(uprobe_link, "attach_uprobe_long_event_name"))
|
||||
goto cleanup;
|
||||
skel->links.handle_uprobe = uprobe_link;
|
||||
|
||||
uprobe_opts.retprobe = true;
|
||||
uretprobe_link = bpf_program__attach_uprobe_opts(skel->progs.handle_uretprobe,
|
||||
-1 /* any pid */,
|
||||
path,
|
||||
uprobe_offset, &uprobe_opts);
|
||||
if (!ASSERT_OK_PTR(uretprobe_link, "attach_uretprobe_long_event_name"))
|
||||
goto cleanup;
|
||||
skel->links.handle_uretprobe = uretprobe_link;
|
||||
|
||||
cleanup:
|
||||
test_attach_probe_manual__destroy(skel);
|
||||
}
|
||||
|
||||
/* attach kprobe/kretprobe long event name testings */
|
||||
static void test_attach_kprobe_long_event_name(void)
|
||||
{
|
||||
DECLARE_LIBBPF_OPTS(bpf_kprobe_opts, kprobe_opts);
|
||||
struct bpf_link *kprobe_link, *kretprobe_link;
|
||||
struct test_attach_probe_manual *skel;
|
||||
|
||||
skel = test_attach_probe_manual__open_and_load();
|
||||
if (!ASSERT_OK_PTR(skel, "skel_kprobe_manual_open_and_load"))
|
||||
return;
|
||||
|
||||
/* manual-attach kprobe/kretprobe */
|
||||
kprobe_opts.attach_mode = PROBE_ATTACH_MODE_LEGACY;
|
||||
kprobe_opts.retprobe = false;
|
||||
kprobe_link = bpf_program__attach_kprobe_opts(skel->progs.handle_kprobe,
|
||||
"bpf_testmod_looooooooooooooooooooooooooooooong_name",
|
||||
&kprobe_opts);
|
||||
if (!ASSERT_OK_PTR(kprobe_link, "attach_kprobe_long_event_name"))
|
||||
goto cleanup;
|
||||
skel->links.handle_kprobe = kprobe_link;
|
||||
|
||||
kprobe_opts.retprobe = true;
|
||||
kretprobe_link = bpf_program__attach_kprobe_opts(skel->progs.handle_kretprobe,
|
||||
"bpf_testmod_looooooooooooooooooooooooooooooong_name",
|
||||
&kprobe_opts);
|
||||
if (!ASSERT_OK_PTR(kretprobe_link, "attach_kretprobe_long_event_name"))
|
||||
goto cleanup;
|
||||
skel->links.handle_kretprobe = kretprobe_link;
|
||||
|
||||
cleanup:
|
||||
test_attach_probe_manual__destroy(skel);
|
||||
}
|
||||
|
||||
static void test_attach_probe_auto(struct test_attach_probe *skel)
|
||||
{
|
||||
struct bpf_link *uprobe_err_link;
|
||||
@@ -323,6 +402,11 @@ void test_attach_probe(void)
|
||||
if (test__start_subtest("uprobe-ref_ctr"))
|
||||
test_uprobe_ref_ctr(skel);
|
||||
|
||||
if (test__start_subtest("uprobe-long_name"))
|
||||
test_attach_uprobe_long_event_name();
|
||||
if (test__start_subtest("kprobe-long_name"))
|
||||
test_attach_kprobe_long_event_name();
|
||||
|
||||
cleanup:
|
||||
test_attach_probe__destroy(skel);
|
||||
ASSERT_EQ(uprobe_ref_ctr, 0, "uprobe_ref_ctr_cleanup");
|
||||
|
||||
@@ -63,6 +63,12 @@ static void test_bpf_nf_ct(int mode)
|
||||
.repeat = 1,
|
||||
);
|
||||
|
||||
if (SYS_NOFAIL("iptables-legacy --version")) {
|
||||
fprintf(stdout, "Missing required iptables-legacy tool\n");
|
||||
test__skip();
|
||||
return;
|
||||
}
|
||||
|
||||
skel = test_bpf_nf__open_and_load();
|
||||
if (!ASSERT_OK_PTR(skel, "test_bpf_nf__open_and_load"))
|
||||
return;
|
||||
|
||||
@@ -440,6 +440,105 @@ cleanup:
|
||||
btf__free(btf1);
|
||||
}
|
||||
|
||||
/* Ensure module split BTF dedup worked correctly; when dedup fails badly
|
||||
* core kernel types are in split BTF also, so ensure that references to
|
||||
* such types point at base - not split - BTF.
|
||||
*
|
||||
* bpf_testmod_test_write() has multiple core kernel type parameters;
|
||||
*
|
||||
* ssize_t
|
||||
* bpf_testmod_test_write(struct file *file, struct kobject *kobj,
|
||||
* struct bin_attribute *bin_attr,
|
||||
* char *buf, loff_t off, size_t len);
|
||||
*
|
||||
* Ensure each of the FUNC_PROTO params is a core kernel type.
|
||||
*
|
||||
* Do the same for
|
||||
*
|
||||
* __bpf_kfunc struct sock *bpf_kfunc_call_test3(struct sock *sk);
|
||||
*
|
||||
* ...and
|
||||
*
|
||||
* __bpf_kfunc void bpf_kfunc_call_test_pass_ctx(struct __sk_buff *skb);
|
||||
*
|
||||
*/
|
||||
const char *mod_funcs[] = {
|
||||
"bpf_testmod_test_write",
|
||||
"bpf_kfunc_call_test3",
|
||||
"bpf_kfunc_call_test_pass_ctx"
|
||||
};
|
||||
|
||||
static void test_split_module(void)
|
||||
{
|
||||
struct btf *vmlinux_btf, *btf1 = NULL;
|
||||
int i, nr_base_types;
|
||||
|
||||
vmlinux_btf = btf__load_vmlinux_btf();
|
||||
if (!ASSERT_OK_PTR(vmlinux_btf, "vmlinux_btf"))
|
||||
return;
|
||||
nr_base_types = btf__type_cnt(vmlinux_btf);
|
||||
if (!ASSERT_GT(nr_base_types, 0, "nr_base_types"))
|
||||
goto cleanup;
|
||||
|
||||
btf1 = btf__parse_split("/sys/kernel/btf/bpf_testmod", vmlinux_btf);
|
||||
if (!ASSERT_OK_PTR(btf1, "split_btf"))
|
||||
return;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(mod_funcs); i++) {
|
||||
const struct btf_param *p;
|
||||
const struct btf_type *t;
|
||||
__u16 vlen;
|
||||
__u32 id;
|
||||
int j;
|
||||
|
||||
id = btf__find_by_name_kind(btf1, mod_funcs[i], BTF_KIND_FUNC);
|
||||
if (!ASSERT_GE(id, nr_base_types, "func_id"))
|
||||
goto cleanup;
|
||||
t = btf__type_by_id(btf1, id);
|
||||
if (!ASSERT_OK_PTR(t, "func_id_type"))
|
||||
goto cleanup;
|
||||
t = btf__type_by_id(btf1, t->type);
|
||||
if (!ASSERT_OK_PTR(t, "func_proto_id_type"))
|
||||
goto cleanup;
|
||||
if (!ASSERT_EQ(btf_is_func_proto(t), true, "is_func_proto"))
|
||||
goto cleanup;
|
||||
vlen = btf_vlen(t);
|
||||
|
||||
for (j = 0, p = btf_params(t); j < vlen; j++, p++) {
|
||||
/* bpf_testmod uses resilient split BTF, so any
|
||||
* reference types will be added to split BTF and their
|
||||
* associated targets will be base BTF types; for example
|
||||
* for a "struct sock *" the PTR will be in split BTF
|
||||
* while the "struct sock" will be in base.
|
||||
*
|
||||
* In some cases like loff_t we have to resolve
|
||||
* multiple typedefs hence the while() loop below.
|
||||
*
|
||||
* Note that resilient split BTF generation depends
|
||||
* on pahole version, so we do not assert that
|
||||
* reference types are in split BTF, as if pahole
|
||||
* does not support resilient split BTF they will
|
||||
* also be base BTF types.
|
||||
*/
|
||||
id = p->type;
|
||||
do {
|
||||
t = btf__type_by_id(btf1, id);
|
||||
if (!ASSERT_OK_PTR(t, "param_ref_type"))
|
||||
goto cleanup;
|
||||
if (!btf_is_mod(t) && !btf_is_ptr(t) && !btf_is_typedef(t))
|
||||
break;
|
||||
id = t->type;
|
||||
} while (true);
|
||||
|
||||
if (!ASSERT_LT(id, nr_base_types, "verify_base_type"))
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
cleanup:
|
||||
btf__free(btf1);
|
||||
btf__free(vmlinux_btf);
|
||||
}
|
||||
|
||||
void test_btf_dedup_split()
|
||||
{
|
||||
if (test__start_subtest("split_simple"))
|
||||
@@ -450,4 +549,6 @@ void test_btf_dedup_split()
|
||||
test_split_fwd_resolve();
|
||||
if (test__start_subtest("split_dup_struct_in_cu"))
|
||||
test_split_dup_struct_in_cu();
|
||||
if (test__start_subtest("split_module"))
|
||||
test_split_module();
|
||||
}
|
||||
|
||||
@@ -12,10 +12,11 @@ static void btf_dump_printf(void *ctx, const char *fmt, va_list args)
|
||||
vfprintf(ctx, fmt, args);
|
||||
}
|
||||
|
||||
void test_btf_split() {
|
||||
static void __test_btf_split(bool multi)
|
||||
{
|
||||
struct btf_dump *d = NULL;
|
||||
const struct btf_type *t;
|
||||
struct btf *btf1, *btf2;
|
||||
struct btf *btf1, *btf2, *btf3 = NULL;
|
||||
int str_off, i, err;
|
||||
|
||||
btf1 = btf__new_empty();
|
||||
@@ -63,14 +64,46 @@ void test_btf_split() {
|
||||
ASSERT_EQ(btf_vlen(t), 3, "split_struct_vlen");
|
||||
ASSERT_STREQ(btf__str_by_offset(btf2, t->name_off), "s2", "split_struct_name");
|
||||
|
||||
if (multi) {
|
||||
btf3 = btf__new_empty_split(btf2);
|
||||
if (!ASSERT_OK_PTR(btf3, "multi_split_btf"))
|
||||
goto cleanup;
|
||||
} else {
|
||||
btf3 = btf2;
|
||||
}
|
||||
|
||||
btf__add_union(btf3, "u1", 16); /* [5] union u1 { */
|
||||
btf__add_field(btf3, "f1", 4, 0, 0); /* struct s2 f1; */
|
||||
btf__add_field(btf3, "uf2", 1, 0, 0); /* int f2; */
|
||||
/* } */
|
||||
|
||||
if (multi) {
|
||||
t = btf__type_by_id(btf2, 5);
|
||||
ASSERT_NULL(t, "multisplit_type_in_first_split");
|
||||
}
|
||||
|
||||
t = btf__type_by_id(btf3, 5);
|
||||
if (!ASSERT_OK_PTR(t, "split_union_type"))
|
||||
goto cleanup;
|
||||
ASSERT_EQ(btf_is_union(t), true, "split_union_kind");
|
||||
ASSERT_EQ(btf_vlen(t), 2, "split_union_vlen");
|
||||
ASSERT_STREQ(btf__str_by_offset(btf3, t->name_off), "u1", "split_union_name");
|
||||
ASSERT_EQ(btf__type_cnt(btf3), 6, "split_type_cnt");
|
||||
|
||||
t = btf__type_by_id(btf3, 1);
|
||||
if (!ASSERT_OK_PTR(t, "split_base_type"))
|
||||
goto cleanup;
|
||||
ASSERT_EQ(btf_is_int(t), true, "split_base_int");
|
||||
ASSERT_STREQ(btf__str_by_offset(btf3, t->name_off), "int", "split_base_type_name");
|
||||
|
||||
/* BTF-to-C dump of split BTF */
|
||||
dump_buf_file = open_memstream(&dump_buf, &dump_buf_sz);
|
||||
if (!ASSERT_OK_PTR(dump_buf_file, "dump_memstream"))
|
||||
return;
|
||||
d = btf_dump__new(btf2, btf_dump_printf, dump_buf_file, NULL);
|
||||
d = btf_dump__new(btf3, btf_dump_printf, dump_buf_file, NULL);
|
||||
if (!ASSERT_OK_PTR(d, "btf_dump__new"))
|
||||
goto cleanup;
|
||||
for (i = 1; i < btf__type_cnt(btf2); i++) {
|
||||
for (i = 1; i < btf__type_cnt(btf3); i++) {
|
||||
err = btf_dump__dump_type(d, i);
|
||||
ASSERT_OK(err, "dump_type_ok");
|
||||
}
|
||||
@@ -79,12 +112,15 @@ void test_btf_split() {
|
||||
ASSERT_STREQ(dump_buf,
|
||||
"struct s1 {\n"
|
||||
" int f1;\n"
|
||||
"};\n"
|
||||
"\n"
|
||||
"};\n\n"
|
||||
"struct s2 {\n"
|
||||
" struct s1 f1;\n"
|
||||
" int f2;\n"
|
||||
" int *f3;\n"
|
||||
"};\n\n"
|
||||
"union u1 {\n"
|
||||
" struct s2 f1;\n"
|
||||
" int uf2;\n"
|
||||
"};\n\n", "c_dump");
|
||||
|
||||
cleanup:
|
||||
@@ -94,4 +130,14 @@ cleanup:
|
||||
btf_dump__free(d);
|
||||
btf__free(btf1);
|
||||
btf__free(btf2);
|
||||
if (btf2 != btf3)
|
||||
btf__free(btf3);
|
||||
}
|
||||
|
||||
void test_btf_split(void)
|
||||
{
|
||||
if (test__start_subtest("single_split"))
|
||||
__test_btf_split(false);
|
||||
if (test__start_subtest("multi_split"))
|
||||
__test_btf_split(true);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
|
||||
/* Copyright (c) 2025 Isovalent */
|
||||
|
||||
#include <test_progs.h>
|
||||
#include <bpf/btf.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static void test_btf_mmap_sysfs(const char *path, struct btf *base)
|
||||
{
|
||||
struct stat st;
|
||||
__u64 btf_size, end;
|
||||
void *raw_data = NULL;
|
||||
int fd = -1;
|
||||
long page_size;
|
||||
struct btf *btf = NULL;
|
||||
|
||||
page_size = sysconf(_SC_PAGESIZE);
|
||||
if (!ASSERT_GE(page_size, 0, "get_page_size"))
|
||||
goto cleanup;
|
||||
|
||||
if (!ASSERT_OK(stat(path, &st), "stat_btf"))
|
||||
goto cleanup;
|
||||
|
||||
btf_size = st.st_size;
|
||||
end = (btf_size + page_size - 1) / page_size * page_size;
|
||||
|
||||
fd = open(path, O_RDONLY);
|
||||
if (!ASSERT_GE(fd, 0, "open_btf"))
|
||||
goto cleanup;
|
||||
|
||||
raw_data = mmap(NULL, btf_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
|
||||
if (!ASSERT_EQ(raw_data, MAP_FAILED, "mmap_btf_writable"))
|
||||
goto cleanup;
|
||||
|
||||
raw_data = mmap(NULL, btf_size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
if (!ASSERT_EQ(raw_data, MAP_FAILED, "mmap_btf_shared"))
|
||||
goto cleanup;
|
||||
|
||||
raw_data = mmap(NULL, end + 1, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (!ASSERT_EQ(raw_data, MAP_FAILED, "mmap_btf_invalid_size"))
|
||||
goto cleanup;
|
||||
|
||||
raw_data = mmap(NULL, end, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (!ASSERT_OK_PTR(raw_data, "mmap_btf"))
|
||||
goto cleanup;
|
||||
|
||||
if (!ASSERT_EQ(mprotect(raw_data, btf_size, PROT_READ | PROT_WRITE), -1,
|
||||
"mprotect_writable"))
|
||||
goto cleanup;
|
||||
|
||||
if (!ASSERT_EQ(mprotect(raw_data, btf_size, PROT_READ | PROT_EXEC), -1,
|
||||
"mprotect_executable"))
|
||||
goto cleanup;
|
||||
|
||||
/* Check padding is zeroed */
|
||||
for (int i = btf_size; i < end; i++) {
|
||||
if (((__u8 *)raw_data)[i] != 0) {
|
||||
PRINT_FAIL("tail of BTF is not zero at page offset %d\n", i);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
btf = btf__new_split(raw_data, btf_size, base);
|
||||
if (!ASSERT_OK_PTR(btf, "parse_btf"))
|
||||
goto cleanup;
|
||||
|
||||
cleanup:
|
||||
btf__free(btf);
|
||||
if (raw_data && raw_data != MAP_FAILED)
|
||||
munmap(raw_data, btf_size);
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
}
|
||||
|
||||
void test_btf_sysfs(void)
|
||||
{
|
||||
test_btf_mmap_sysfs("/sys/kernel/btf/vmlinux", NULL);
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2025 Google */
|
||||
|
||||
#include <test_progs.h>
|
||||
#include <bpf/libbpf.h>
|
||||
#include <bpf/btf.h>
|
||||
#include "dmabuf_iter.skel.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <linux/dma-buf.h>
|
||||
#include <linux/dma-heap.h>
|
||||
#include <linux/udmabuf.h>
|
||||
|
||||
static int udmabuf = -1;
|
||||
static const char udmabuf_test_buffer_name[DMA_BUF_NAME_LEN] = "udmabuf_test_buffer_for_iter";
|
||||
static size_t udmabuf_test_buffer_size;
|
||||
static int sysheap_dmabuf = -1;
|
||||
static const char sysheap_test_buffer_name[DMA_BUF_NAME_LEN] = "sysheap_test_buffer_for_iter";
|
||||
static size_t sysheap_test_buffer_size;
|
||||
|
||||
static int create_udmabuf(void)
|
||||
{
|
||||
struct udmabuf_create create;
|
||||
int dev_udmabuf, memfd, local_udmabuf;
|
||||
|
||||
udmabuf_test_buffer_size = 10 * getpagesize();
|
||||
|
||||
if (!ASSERT_LE(sizeof(udmabuf_test_buffer_name), DMA_BUF_NAME_LEN, "NAMETOOLONG"))
|
||||
return -1;
|
||||
|
||||
memfd = memfd_create("memfd_test", MFD_ALLOW_SEALING);
|
||||
if (!ASSERT_OK_FD(memfd, "memfd_create"))
|
||||
return -1;
|
||||
|
||||
if (!ASSERT_OK(ftruncate(memfd, udmabuf_test_buffer_size), "ftruncate"))
|
||||
goto close_memfd;
|
||||
|
||||
if (!ASSERT_OK(fcntl(memfd, F_ADD_SEALS, F_SEAL_SHRINK), "seal"))
|
||||
goto close_memfd;
|
||||
|
||||
dev_udmabuf = open("/dev/udmabuf", O_RDONLY);
|
||||
if (!ASSERT_OK_FD(dev_udmabuf, "open udmabuf"))
|
||||
goto close_memfd;
|
||||
|
||||
memset(&create, 0, sizeof(create));
|
||||
create.memfd = memfd;
|
||||
create.flags = UDMABUF_FLAGS_CLOEXEC;
|
||||
create.offset = 0;
|
||||
create.size = udmabuf_test_buffer_size;
|
||||
|
||||
local_udmabuf = ioctl(dev_udmabuf, UDMABUF_CREATE, &create);
|
||||
close(dev_udmabuf);
|
||||
if (!ASSERT_OK_FD(local_udmabuf, "udmabuf_create"))
|
||||
goto close_memfd;
|
||||
|
||||
if (!ASSERT_OK(ioctl(local_udmabuf, DMA_BUF_SET_NAME_B, udmabuf_test_buffer_name), "name"))
|
||||
goto close_udmabuf;
|
||||
|
||||
return local_udmabuf;
|
||||
|
||||
close_udmabuf:
|
||||
close(local_udmabuf);
|
||||
close_memfd:
|
||||
close(memfd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int create_sys_heap_dmabuf(void)
|
||||
{
|
||||
sysheap_test_buffer_size = 20 * getpagesize();
|
||||
|
||||
struct dma_heap_allocation_data data = {
|
||||
.len = sysheap_test_buffer_size,
|
||||
.fd = 0,
|
||||
.fd_flags = O_RDWR | O_CLOEXEC,
|
||||
.heap_flags = 0,
|
||||
};
|
||||
int heap_fd, ret;
|
||||
|
||||
if (!ASSERT_LE(sizeof(sysheap_test_buffer_name), DMA_BUF_NAME_LEN, "NAMETOOLONG"))
|
||||
return -1;
|
||||
|
||||
heap_fd = open("/dev/dma_heap/system", O_RDONLY);
|
||||
if (!ASSERT_OK_FD(heap_fd, "open dma heap"))
|
||||
return -1;
|
||||
|
||||
ret = ioctl(heap_fd, DMA_HEAP_IOCTL_ALLOC, &data);
|
||||
close(heap_fd);
|
||||
if (!ASSERT_OK(ret, "syheap alloc"))
|
||||
return -1;
|
||||
|
||||
if (!ASSERT_OK(ioctl(data.fd, DMA_BUF_SET_NAME_B, sysheap_test_buffer_name), "name"))
|
||||
goto close_sysheap_dmabuf;
|
||||
|
||||
return data.fd;
|
||||
|
||||
close_sysheap_dmabuf:
|
||||
close(data.fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int create_test_buffers(void)
|
||||
{
|
||||
udmabuf = create_udmabuf();
|
||||
sysheap_dmabuf = create_sys_heap_dmabuf();
|
||||
|
||||
if (udmabuf < 0 || sysheap_dmabuf < 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void destroy_test_buffers(void)
|
||||
{
|
||||
close(udmabuf);
|
||||
udmabuf = -1;
|
||||
|
||||
close(sysheap_dmabuf);
|
||||
sysheap_dmabuf = -1;
|
||||
}
|
||||
|
||||
enum Fields { INODE, SIZE, NAME, EXPORTER, FIELD_COUNT };
|
||||
struct DmabufInfo {
|
||||
unsigned long inode;
|
||||
unsigned long size;
|
||||
char name[DMA_BUF_NAME_LEN];
|
||||
char exporter[32];
|
||||
};
|
||||
|
||||
static bool check_dmabuf_info(const struct DmabufInfo *bufinfo,
|
||||
unsigned long size,
|
||||
const char *name, const char *exporter)
|
||||
{
|
||||
return size == bufinfo->size &&
|
||||
!strcmp(name, bufinfo->name) &&
|
||||
!strcmp(exporter, bufinfo->exporter);
|
||||
}
|
||||
|
||||
static void subtest_dmabuf_iter_check_no_infinite_reads(struct dmabuf_iter *skel)
|
||||
{
|
||||
int iter_fd;
|
||||
char buf[256];
|
||||
|
||||
iter_fd = bpf_iter_create(bpf_link__fd(skel->links.dmabuf_collector));
|
||||
if (!ASSERT_OK_FD(iter_fd, "iter_create"))
|
||||
return;
|
||||
|
||||
while (read(iter_fd, buf, sizeof(buf)) > 0)
|
||||
; /* Read out all contents */
|
||||
|
||||
/* Next reads should return 0 */
|
||||
ASSERT_EQ(read(iter_fd, buf, sizeof(buf)), 0, "read");
|
||||
|
||||
close(iter_fd);
|
||||
}
|
||||
|
||||
static void subtest_dmabuf_iter_check_default_iter(struct dmabuf_iter *skel)
|
||||
{
|
||||
bool found_test_sysheap_dmabuf = false;
|
||||
bool found_test_udmabuf = false;
|
||||
struct DmabufInfo bufinfo;
|
||||
size_t linesize = 0;
|
||||
char *line = NULL;
|
||||
FILE *iter_file;
|
||||
int iter_fd, f = INODE;
|
||||
|
||||
iter_fd = bpf_iter_create(bpf_link__fd(skel->links.dmabuf_collector));
|
||||
if (!ASSERT_OK_FD(iter_fd, "iter_create"))
|
||||
return;
|
||||
|
||||
iter_file = fdopen(iter_fd, "r");
|
||||
if (!ASSERT_OK_PTR(iter_file, "fdopen"))
|
||||
goto close_iter_fd;
|
||||
|
||||
while (getline(&line, &linesize, iter_file) != -1) {
|
||||
if (f % FIELD_COUNT == INODE) {
|
||||
ASSERT_EQ(sscanf(line, "%ld", &bufinfo.inode), 1,
|
||||
"read inode");
|
||||
} else if (f % FIELD_COUNT == SIZE) {
|
||||
ASSERT_EQ(sscanf(line, "%ld", &bufinfo.size), 1,
|
||||
"read size");
|
||||
} else if (f % FIELD_COUNT == NAME) {
|
||||
ASSERT_EQ(sscanf(line, "%s", bufinfo.name), 1,
|
||||
"read name");
|
||||
} else if (f % FIELD_COUNT == EXPORTER) {
|
||||
ASSERT_EQ(sscanf(line, "%31s", bufinfo.exporter), 1,
|
||||
"read exporter");
|
||||
|
||||
if (check_dmabuf_info(&bufinfo,
|
||||
sysheap_test_buffer_size,
|
||||
sysheap_test_buffer_name,
|
||||
"system"))
|
||||
found_test_sysheap_dmabuf = true;
|
||||
else if (check_dmabuf_info(&bufinfo,
|
||||
udmabuf_test_buffer_size,
|
||||
udmabuf_test_buffer_name,
|
||||
"udmabuf"))
|
||||
found_test_udmabuf = true;
|
||||
}
|
||||
++f;
|
||||
}
|
||||
|
||||
ASSERT_EQ(f % FIELD_COUNT, INODE, "number of fields");
|
||||
|
||||
ASSERT_TRUE(found_test_sysheap_dmabuf, "found_test_sysheap_dmabuf");
|
||||
ASSERT_TRUE(found_test_udmabuf, "found_test_udmabuf");
|
||||
|
||||
free(line);
|
||||
fclose(iter_file);
|
||||
close_iter_fd:
|
||||
close(iter_fd);
|
||||
}
|
||||
|
||||
static void subtest_dmabuf_iter_check_open_coded(struct dmabuf_iter *skel, int map_fd)
|
||||
{
|
||||
LIBBPF_OPTS(bpf_test_run_opts, topts);
|
||||
char key[DMA_BUF_NAME_LEN];
|
||||
int err, fd;
|
||||
bool found;
|
||||
|
||||
/* No need to attach it, just run it directly */
|
||||
fd = bpf_program__fd(skel->progs.iter_dmabuf_for_each);
|
||||
|
||||
err = bpf_prog_test_run_opts(fd, &topts);
|
||||
if (!ASSERT_OK(err, "test_run_opts err"))
|
||||
return;
|
||||
if (!ASSERT_OK(topts.retval, "test_run_opts retval"))
|
||||
return;
|
||||
|
||||
if (!ASSERT_OK(bpf_map_get_next_key(map_fd, NULL, key), "get next key"))
|
||||
return;
|
||||
|
||||
do {
|
||||
ASSERT_OK(bpf_map_lookup_elem(map_fd, key, &found), "lookup");
|
||||
ASSERT_TRUE(found, "found test buffer");
|
||||
} while (bpf_map_get_next_key(map_fd, key, key));
|
||||
}
|
||||
|
||||
void test_dmabuf_iter(void)
|
||||
{
|
||||
struct dmabuf_iter *skel = NULL;
|
||||
int map_fd;
|
||||
const bool f = false;
|
||||
|
||||
skel = dmabuf_iter__open_and_load();
|
||||
if (!ASSERT_OK_PTR(skel, "dmabuf_iter__open_and_load"))
|
||||
return;
|
||||
|
||||
map_fd = bpf_map__fd(skel->maps.testbuf_hash);
|
||||
if (!ASSERT_OK_FD(map_fd, "map_fd"))
|
||||
goto destroy_skel;
|
||||
|
||||
if (!ASSERT_OK(bpf_map_update_elem(map_fd, udmabuf_test_buffer_name, &f, BPF_ANY),
|
||||
"insert udmabuf"))
|
||||
goto destroy_skel;
|
||||
if (!ASSERT_OK(bpf_map_update_elem(map_fd, sysheap_test_buffer_name, &f, BPF_ANY),
|
||||
"insert sysheap buffer"))
|
||||
goto destroy_skel;
|
||||
|
||||
if (!ASSERT_OK(create_test_buffers(), "create_test_buffers"))
|
||||
goto destroy;
|
||||
|
||||
if (!ASSERT_OK(dmabuf_iter__attach(skel), "skel_attach"))
|
||||
goto destroy;
|
||||
|
||||
if (test__start_subtest("no_infinite_reads"))
|
||||
subtest_dmabuf_iter_check_no_infinite_reads(skel);
|
||||
if (test__start_subtest("default_iter"))
|
||||
subtest_dmabuf_iter_check_default_iter(skel);
|
||||
if (test__start_subtest("open_coded"))
|
||||
subtest_dmabuf_iter_check_open_coded(skel, map_fd);
|
||||
|
||||
destroy:
|
||||
destroy_test_buffers();
|
||||
destroy_skel:
|
||||
dmabuf_iter__destroy(skel);
|
||||
}
|
||||
@@ -33,10 +33,19 @@ static struct {
|
||||
{"test_dynptr_skb_no_buff", SETUP_SKB_PROG},
|
||||
{"test_dynptr_skb_strcmp", SETUP_SKB_PROG},
|
||||
{"test_dynptr_skb_tp_btf", SETUP_SKB_PROG_TP},
|
||||
{"test_probe_read_user_dynptr", SETUP_XDP_PROG},
|
||||
{"test_probe_read_kernel_dynptr", SETUP_XDP_PROG},
|
||||
{"test_probe_read_user_str_dynptr", SETUP_XDP_PROG},
|
||||
{"test_probe_read_kernel_str_dynptr", SETUP_XDP_PROG},
|
||||
{"test_copy_from_user_dynptr", SETUP_SYSCALL_SLEEP},
|
||||
{"test_copy_from_user_str_dynptr", SETUP_SYSCALL_SLEEP},
|
||||
{"test_copy_from_user_task_dynptr", SETUP_SYSCALL_SLEEP},
|
||||
{"test_copy_from_user_task_str_dynptr", SETUP_SYSCALL_SLEEP},
|
||||
};
|
||||
|
||||
static void verify_success(const char *prog_name, enum test_setup_type setup_type)
|
||||
{
|
||||
char user_data[384] = {[0 ... 382] = 'a', '\0'};
|
||||
struct dynptr_success *skel;
|
||||
struct bpf_program *prog;
|
||||
struct bpf_link *link;
|
||||
@@ -58,6 +67,10 @@ static void verify_success(const char *prog_name, enum test_setup_type setup_typ
|
||||
if (!ASSERT_OK(err, "dynptr_success__load"))
|
||||
goto cleanup;
|
||||
|
||||
skel->bss->user_ptr = user_data;
|
||||
skel->data->test_len[0] = sizeof(user_data);
|
||||
memcpy(skel->bss->expected_str, user_data, sizeof(user_data));
|
||||
|
||||
switch (setup_type) {
|
||||
case SETUP_SYSCALL_SLEEP:
|
||||
link = bpf_program__attach(prog);
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (C) 2025. Huawei Technologies Co., Ltd */
|
||||
#define _GNU_SOURCE
|
||||
#include <stdbool.h>
|
||||
#include <test_progs.h>
|
||||
#include "fd_htab_lookup.skel.h"
|
||||
|
||||
struct htab_op_ctx {
|
||||
int fd;
|
||||
int loop;
|
||||
unsigned int entries;
|
||||
bool stop;
|
||||
};
|
||||
|
||||
#define ERR_TO_RETVAL(where, err) ((void *)(long)(((where) << 12) | (-err)))
|
||||
|
||||
static void *htab_lookup_fn(void *arg)
|
||||
{
|
||||
struct htab_op_ctx *ctx = arg;
|
||||
int i = 0;
|
||||
|
||||
while (i++ < ctx->loop && !ctx->stop) {
|
||||
unsigned int j;
|
||||
|
||||
for (j = 0; j < ctx->entries; j++) {
|
||||
unsigned int key = j, zero = 0, value;
|
||||
int inner_fd, err;
|
||||
|
||||
err = bpf_map_lookup_elem(ctx->fd, &key, &value);
|
||||
if (err) {
|
||||
ctx->stop = true;
|
||||
return ERR_TO_RETVAL(1, err);
|
||||
}
|
||||
|
||||
inner_fd = bpf_map_get_fd_by_id(value);
|
||||
if (inner_fd < 0) {
|
||||
/* The old map has been freed */
|
||||
if (inner_fd == -ENOENT)
|
||||
continue;
|
||||
ctx->stop = true;
|
||||
return ERR_TO_RETVAL(2, inner_fd);
|
||||
}
|
||||
|
||||
err = bpf_map_lookup_elem(inner_fd, &zero, &value);
|
||||
if (err) {
|
||||
close(inner_fd);
|
||||
ctx->stop = true;
|
||||
return ERR_TO_RETVAL(3, err);
|
||||
}
|
||||
close(inner_fd);
|
||||
|
||||
if (value != key) {
|
||||
ctx->stop = true;
|
||||
return ERR_TO_RETVAL(4, -EINVAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *htab_update_fn(void *arg)
|
||||
{
|
||||
struct htab_op_ctx *ctx = arg;
|
||||
int i = 0;
|
||||
|
||||
while (i++ < ctx->loop && !ctx->stop) {
|
||||
unsigned int j;
|
||||
|
||||
for (j = 0; j < ctx->entries; j++) {
|
||||
unsigned int key = j, zero = 0;
|
||||
int inner_fd, err;
|
||||
|
||||
inner_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, NULL, 4, 4, 1, NULL);
|
||||
if (inner_fd < 0) {
|
||||
ctx->stop = true;
|
||||
return ERR_TO_RETVAL(1, inner_fd);
|
||||
}
|
||||
|
||||
err = bpf_map_update_elem(inner_fd, &zero, &key, 0);
|
||||
if (err) {
|
||||
close(inner_fd);
|
||||
ctx->stop = true;
|
||||
return ERR_TO_RETVAL(2, err);
|
||||
}
|
||||
|
||||
err = bpf_map_update_elem(ctx->fd, &key, &inner_fd, BPF_EXIST);
|
||||
if (err) {
|
||||
close(inner_fd);
|
||||
ctx->stop = true;
|
||||
return ERR_TO_RETVAL(3, err);
|
||||
}
|
||||
close(inner_fd);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int setup_htab(int fd, unsigned int entries)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < entries; i++) {
|
||||
unsigned int key = i, zero = 0;
|
||||
int inner_fd, err;
|
||||
|
||||
inner_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, NULL, 4, 4, 1, NULL);
|
||||
if (!ASSERT_OK_FD(inner_fd, "new array"))
|
||||
return -1;
|
||||
|
||||
err = bpf_map_update_elem(inner_fd, &zero, &key, 0);
|
||||
if (!ASSERT_OK(err, "init array")) {
|
||||
close(inner_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
err = bpf_map_update_elem(fd, &key, &inner_fd, 0);
|
||||
if (!ASSERT_OK(err, "init outer")) {
|
||||
close(inner_fd);
|
||||
return -1;
|
||||
}
|
||||
close(inner_fd);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_int_from_env(const char *name, int dft)
|
||||
{
|
||||
const char *value;
|
||||
|
||||
value = getenv(name);
|
||||
if (!value)
|
||||
return dft;
|
||||
|
||||
return atoi(value);
|
||||
}
|
||||
|
||||
void test_fd_htab_lookup(void)
|
||||
{
|
||||
unsigned int i, wr_nr = 8, rd_nr = 16;
|
||||
pthread_t tids[wr_nr + rd_nr];
|
||||
struct fd_htab_lookup *skel;
|
||||
struct htab_op_ctx ctx;
|
||||
int err;
|
||||
|
||||
skel = fd_htab_lookup__open_and_load();
|
||||
if (!ASSERT_OK_PTR(skel, "fd_htab_lookup__open_and_load"))
|
||||
return;
|
||||
|
||||
ctx.fd = bpf_map__fd(skel->maps.outer_map);
|
||||
ctx.loop = get_int_from_env("FD_HTAB_LOOP_NR", 5);
|
||||
ctx.stop = false;
|
||||
ctx.entries = 8;
|
||||
|
||||
err = setup_htab(ctx.fd, ctx.entries);
|
||||
if (err)
|
||||
goto destroy;
|
||||
|
||||
memset(tids, 0, sizeof(tids));
|
||||
for (i = 0; i < wr_nr; i++) {
|
||||
err = pthread_create(&tids[i], NULL, htab_update_fn, &ctx);
|
||||
if (!ASSERT_OK(err, "pthread_create")) {
|
||||
ctx.stop = true;
|
||||
goto reap;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < rd_nr; i++) {
|
||||
err = pthread_create(&tids[i + wr_nr], NULL, htab_lookup_fn, &ctx);
|
||||
if (!ASSERT_OK(err, "pthread_create")) {
|
||||
ctx.stop = true;
|
||||
goto reap;
|
||||
}
|
||||
}
|
||||
|
||||
reap:
|
||||
for (i = 0; i < wr_nr + rd_nr; i++) {
|
||||
void *ret = NULL;
|
||||
char desc[32];
|
||||
|
||||
if (!tids[i])
|
||||
continue;
|
||||
|
||||
snprintf(desc, sizeof(desc), "thread %u", i + 1);
|
||||
err = pthread_join(tids[i], &ret);
|
||||
ASSERT_OK(err, desc);
|
||||
ASSERT_EQ(ret, NULL, desc);
|
||||
}
|
||||
destroy:
|
||||
fd_htab_lookup__destroy(skel);
|
||||
}
|
||||
@@ -37,6 +37,7 @@ static noinline void uprobe_func(void)
|
||||
static int verify_perf_link_info(int fd, enum bpf_perf_event_type type, long addr,
|
||||
ssize_t offset, ssize_t entry_offset)
|
||||
{
|
||||
ssize_t ref_ctr_offset = entry_offset /* ref_ctr_offset for uprobes */;
|
||||
struct bpf_link_info info;
|
||||
__u32 len = sizeof(info);
|
||||
char buf[PATH_MAX];
|
||||
@@ -97,6 +98,7 @@ again:
|
||||
case BPF_PERF_EVENT_UPROBE:
|
||||
case BPF_PERF_EVENT_URETPROBE:
|
||||
ASSERT_EQ(info.perf_event.uprobe.offset, offset, "uprobe_offset");
|
||||
ASSERT_EQ(info.perf_event.uprobe.ref_ctr_offset, ref_ctr_offset, "uprobe_ref_ctr_offset");
|
||||
|
||||
ASSERT_EQ(info.perf_event.uprobe.name_len, strlen(UPROBE_FILE) + 1,
|
||||
"name_len");
|
||||
@@ -241,20 +243,32 @@ static void test_uprobe_fill_link_info(struct test_fill_link_info *skel,
|
||||
.retprobe = type == BPF_PERF_EVENT_URETPROBE,
|
||||
.bpf_cookie = PERF_EVENT_COOKIE,
|
||||
);
|
||||
const char *sema[1] = {
|
||||
"uprobe_link_info_sema_1",
|
||||
};
|
||||
__u64 *ref_ctr_offset;
|
||||
struct bpf_link *link;
|
||||
int link_fd, err;
|
||||
|
||||
err = elf_resolve_syms_offsets("/proc/self/exe", 1, sema,
|
||||
(unsigned long **) &ref_ctr_offset, STT_OBJECT);
|
||||
if (!ASSERT_OK(err, "elf_resolve_syms_offsets_object"))
|
||||
return;
|
||||
|
||||
opts.ref_ctr_offset = *ref_ctr_offset;
|
||||
link = bpf_program__attach_uprobe_opts(skel->progs.uprobe_run,
|
||||
0, /* self pid */
|
||||
UPROBE_FILE, uprobe_offset,
|
||||
&opts);
|
||||
if (!ASSERT_OK_PTR(link, "attach_uprobe"))
|
||||
return;
|
||||
goto out;
|
||||
|
||||
link_fd = bpf_link__fd(link);
|
||||
err = verify_perf_link_info(link_fd, type, 0, uprobe_offset, 0);
|
||||
err = verify_perf_link_info(link_fd, type, 0, uprobe_offset, *ref_ctr_offset);
|
||||
ASSERT_OK(err, "verify_perf_link_info");
|
||||
bpf_link__destroy(link);
|
||||
out:
|
||||
free(ref_ctr_offset);
|
||||
}
|
||||
|
||||
static int verify_kmulti_link_info(int fd, bool retprobe, bool has_cookies)
|
||||
|
||||
@@ -104,7 +104,7 @@ void test_kmem_cache_iter(void)
|
||||
goto destroy;
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
while (read(iter_fd, buf, sizeof(buf) > 0)) {
|
||||
while (read(iter_fd, buf, sizeof(buf)) > 0) {
|
||||
/* Read out all contents */
|
||||
printf("%s", buf);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "linked_list.skel.h"
|
||||
#include "linked_list_fail.skel.h"
|
||||
#include "linked_list_peek.skel.h"
|
||||
|
||||
static char log_buf[1024 * 1024];
|
||||
|
||||
@@ -805,3 +806,8 @@ void test_linked_list(void)
|
||||
test_linked_list_success(LIST_IN_LIST, true);
|
||||
test_linked_list_success(TEST_ALL, false);
|
||||
}
|
||||
|
||||
void test_linked_list_peek(void)
|
||||
{
|
||||
RUN_TESTS(linked_list_peek);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "rbtree_fail.skel.h"
|
||||
#include "rbtree_btf_fail__wrong_node_type.skel.h"
|
||||
#include "rbtree_btf_fail__add_wrong_type.skel.h"
|
||||
#include "rbtree_search.skel.h"
|
||||
|
||||
static void test_rbtree_add_nodes(void)
|
||||
{
|
||||
@@ -187,3 +188,8 @@ void test_rbtree_fail(void)
|
||||
{
|
||||
RUN_TESTS(rbtree_fail);
|
||||
}
|
||||
|
||||
void test_rbtree_search(void)
|
||||
{
|
||||
RUN_TESTS(rbtree_search);
|
||||
}
|
||||
|
||||
@@ -37,8 +37,10 @@ configure_stack(void)
|
||||
tc = popen("tc -V", "r");
|
||||
if (CHECK_FAIL(!tc))
|
||||
return false;
|
||||
if (CHECK_FAIL(!fgets(tc_version, sizeof(tc_version), tc)))
|
||||
if (CHECK_FAIL(!fgets(tc_version, sizeof(tc_version), tc))) {
|
||||
pclose(tc);
|
||||
return false;
|
||||
}
|
||||
if (strstr(tc_version, ", libbpf "))
|
||||
prog = "test_sk_assign_libbpf.bpf.o";
|
||||
else
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#ifndef __SOCKET_HELPERS__
|
||||
#define __SOCKET_HELPERS__
|
||||
|
||||
#include <sys/un.h>
|
||||
#include <linux/vm_sockets.h>
|
||||
|
||||
/* include/linux/net.h */
|
||||
@@ -169,6 +170,15 @@ static inline void init_addr_loopback6(struct sockaddr_storage *ss,
|
||||
*len = sizeof(*addr6);
|
||||
}
|
||||
|
||||
static inline void init_addr_loopback_unix(struct sockaddr_storage *ss,
|
||||
socklen_t *len)
|
||||
{
|
||||
struct sockaddr_un *addr = memset(ss, 0, sizeof(*ss));
|
||||
|
||||
addr->sun_family = AF_UNIX;
|
||||
*len = sizeof(sa_family_t);
|
||||
}
|
||||
|
||||
static inline void init_addr_loopback_vsock(struct sockaddr_storage *ss,
|
||||
socklen_t *len)
|
||||
{
|
||||
@@ -190,6 +200,9 @@ static inline void init_addr_loopback(int family, struct sockaddr_storage *ss,
|
||||
case AF_INET6:
|
||||
init_addr_loopback6(ss, len);
|
||||
return;
|
||||
case AF_UNIX:
|
||||
init_addr_loopback_unix(ss, len);
|
||||
return;
|
||||
case AF_VSOCK:
|
||||
init_addr_loopback_vsock(ss, len);
|
||||
return;
|
||||
@@ -315,21 +328,27 @@ static inline int create_pair(int family, int sotype, int *p0, int *p1)
|
||||
{
|
||||
__close_fd int s, c = -1, p = -1;
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t len = sizeof(addr);
|
||||
socklen_t len;
|
||||
int err;
|
||||
|
||||
s = socket_loopback(family, sotype);
|
||||
if (s < 0)
|
||||
return s;
|
||||
|
||||
err = xgetsockname(s, sockaddr(&addr), &len);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
c = xsocket(family, sotype, 0);
|
||||
if (c < 0)
|
||||
return c;
|
||||
|
||||
init_addr_loopback(family, &addr, &len);
|
||||
err = xbind(c, sockaddr(&addr), len);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
len = sizeof(addr);
|
||||
err = xgetsockname(s, sockaddr(&addr), &len);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = connect(c, sockaddr(&addr), len);
|
||||
if (err) {
|
||||
if (errno != EINPROGRESS) {
|
||||
@@ -391,4 +410,59 @@ static inline int create_socket_pairs(int family, int sotype, int *c0, int *c1,
|
||||
return err;
|
||||
}
|
||||
|
||||
static inline const char *socket_kind_to_str(int sock_fd)
|
||||
{
|
||||
socklen_t opt_len;
|
||||
int domain, type;
|
||||
|
||||
opt_len = sizeof(domain);
|
||||
if (getsockopt(sock_fd, SOL_SOCKET, SO_DOMAIN, &domain, &opt_len))
|
||||
FAIL_ERRNO("getsockopt(SO_DOMAIN)");
|
||||
|
||||
opt_len = sizeof(type);
|
||||
if (getsockopt(sock_fd, SOL_SOCKET, SO_TYPE, &type, &opt_len))
|
||||
FAIL_ERRNO("getsockopt(SO_TYPE)");
|
||||
|
||||
switch (domain) {
|
||||
case AF_INET:
|
||||
switch (type) {
|
||||
case SOCK_STREAM:
|
||||
return "tcp4";
|
||||
case SOCK_DGRAM:
|
||||
return "udp4";
|
||||
}
|
||||
break;
|
||||
case AF_INET6:
|
||||
switch (type) {
|
||||
case SOCK_STREAM:
|
||||
return "tcp6";
|
||||
case SOCK_DGRAM:
|
||||
return "udp6";
|
||||
}
|
||||
break;
|
||||
case AF_UNIX:
|
||||
switch (type) {
|
||||
case SOCK_STREAM:
|
||||
return "u_str";
|
||||
case SOCK_DGRAM:
|
||||
return "u_dgr";
|
||||
case SOCK_SEQPACKET:
|
||||
return "u_seq";
|
||||
}
|
||||
break;
|
||||
case AF_VSOCK:
|
||||
switch (type) {
|
||||
case SOCK_STREAM:
|
||||
return "v_str";
|
||||
case SOCK_DGRAM:
|
||||
return "v_dgr";
|
||||
case SOCK_SEQPACKET:
|
||||
return "v_seq";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return "???";
|
||||
}
|
||||
|
||||
#endif // __SOCKET_HELPERS__
|
||||
|
||||
@@ -5,12 +5,15 @@
|
||||
|
||||
#define MAX_TEST_NAME 80
|
||||
|
||||
#define u32(v) ((u32){(v)})
|
||||
#define u64(v) ((u64){(v)})
|
||||
|
||||
#define __always_unused __attribute__((__unused__))
|
||||
|
||||
#define xbpf_map_delete_elem(fd, key) \
|
||||
({ \
|
||||
int __ret = bpf_map_delete_elem((fd), (key)); \
|
||||
if (__ret < 0) \
|
||||
if (__ret < 0) \
|
||||
FAIL_ERRNO("map_delete"); \
|
||||
__ret; \
|
||||
})
|
||||
@@ -18,7 +21,7 @@
|
||||
#define xbpf_map_lookup_elem(fd, key, val) \
|
||||
({ \
|
||||
int __ret = bpf_map_lookup_elem((fd), (key), (val)); \
|
||||
if (__ret < 0) \
|
||||
if (__ret < 0) \
|
||||
FAIL_ERRNO("map_lookup"); \
|
||||
__ret; \
|
||||
})
|
||||
@@ -26,7 +29,7 @@
|
||||
#define xbpf_map_update_elem(fd, key, val, flags) \
|
||||
({ \
|
||||
int __ret = bpf_map_update_elem((fd), (key), (val), (flags)); \
|
||||
if (__ret < 0) \
|
||||
if (__ret < 0) \
|
||||
FAIL_ERRNO("map_update"); \
|
||||
__ret; \
|
||||
})
|
||||
@@ -35,7 +38,7 @@
|
||||
({ \
|
||||
int __ret = \
|
||||
bpf_prog_attach((prog), (target), (type), (flags)); \
|
||||
if (__ret < 0) \
|
||||
if (__ret < 0) \
|
||||
FAIL_ERRNO("prog_attach(" #type ")"); \
|
||||
__ret; \
|
||||
})
|
||||
@@ -43,7 +46,7 @@
|
||||
#define xbpf_prog_detach2(prog, target, type) \
|
||||
({ \
|
||||
int __ret = bpf_prog_detach2((prog), (target), (type)); \
|
||||
if (__ret < 0) \
|
||||
if (__ret < 0) \
|
||||
FAIL_ERRNO("prog_detach2(" #type ")"); \
|
||||
__ret; \
|
||||
})
|
||||
@@ -66,21 +69,15 @@
|
||||
__ret; \
|
||||
})
|
||||
|
||||
static inline int add_to_sockmap(int sock_mapfd, int fd1, int fd2)
|
||||
static inline int add_to_sockmap(int mapfd, int fd1, int fd2)
|
||||
{
|
||||
u64 value;
|
||||
u32 key;
|
||||
int err;
|
||||
|
||||
key = 0;
|
||||
value = fd1;
|
||||
err = xbpf_map_update_elem(sock_mapfd, &key, &value, BPF_NOEXIST);
|
||||
err = xbpf_map_update_elem(mapfd, &u32(0), &u64(fd1), BPF_NOEXIST);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
key = 1;
|
||||
value = fd2;
|
||||
return xbpf_map_update_elem(sock_mapfd, &key, &value, BPF_NOEXIST);
|
||||
return xbpf_map_update_elem(mapfd, &u32(1), &u64(fd2), BPF_NOEXIST);
|
||||
}
|
||||
|
||||
#endif // __SOCKMAP_HELPERS__
|
||||
|
||||
@@ -3,76 +3,62 @@
|
||||
/*
|
||||
* Tests for sockmap/sockhash holding kTLS sockets.
|
||||
*/
|
||||
|
||||
#include <error.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <linux/tls.h>
|
||||
#include "test_progs.h"
|
||||
#include "sockmap_helpers.h"
|
||||
#include "test_skmsg_load_helpers.skel.h"
|
||||
#include "test_sockmap_ktls.skel.h"
|
||||
|
||||
#define MAX_TEST_NAME 80
|
||||
#define TCP_ULP 31
|
||||
|
||||
static int tcp_server(int family)
|
||||
static int init_ktls_pairs(int c, int p)
|
||||
{
|
||||
int err, s;
|
||||
int err;
|
||||
struct tls12_crypto_info_aes_gcm_128 crypto_rx;
|
||||
struct tls12_crypto_info_aes_gcm_128 crypto_tx;
|
||||
|
||||
s = socket(family, SOCK_STREAM, 0);
|
||||
if (!ASSERT_GE(s, 0, "socket"))
|
||||
return -1;
|
||||
|
||||
err = listen(s, SOMAXCONN);
|
||||
if (!ASSERT_OK(err, "listen"))
|
||||
return -1;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static int disconnect(int fd)
|
||||
{
|
||||
struct sockaddr unspec = { AF_UNSPEC };
|
||||
|
||||
return connect(fd, &unspec, sizeof(unspec));
|
||||
}
|
||||
|
||||
/* Disconnect (unhash) a kTLS socket after removing it from sockmap. */
|
||||
static void test_sockmap_ktls_disconnect_after_delete(int family, int map)
|
||||
{
|
||||
struct sockaddr_storage addr = {0};
|
||||
socklen_t len = sizeof(addr);
|
||||
int err, cli, srv, zero = 0;
|
||||
|
||||
srv = tcp_server(family);
|
||||
if (srv == -1)
|
||||
return;
|
||||
|
||||
err = getsockname(srv, (struct sockaddr *)&addr, &len);
|
||||
if (!ASSERT_OK(err, "getsockopt"))
|
||||
goto close_srv;
|
||||
|
||||
cli = socket(family, SOCK_STREAM, 0);
|
||||
if (!ASSERT_GE(cli, 0, "socket"))
|
||||
goto close_srv;
|
||||
|
||||
err = connect(cli, (struct sockaddr *)&addr, len);
|
||||
if (!ASSERT_OK(err, "connect"))
|
||||
goto close_cli;
|
||||
|
||||
err = bpf_map_update_elem(map, &zero, &cli, 0);
|
||||
if (!ASSERT_OK(err, "bpf_map_update_elem"))
|
||||
goto close_cli;
|
||||
|
||||
err = setsockopt(cli, IPPROTO_TCP, TCP_ULP, "tls", strlen("tls"));
|
||||
err = setsockopt(c, IPPROTO_TCP, TCP_ULP, "tls", strlen("tls"));
|
||||
if (!ASSERT_OK(err, "setsockopt(TCP_ULP)"))
|
||||
goto close_cli;
|
||||
goto out;
|
||||
|
||||
err = bpf_map_delete_elem(map, &zero);
|
||||
if (!ASSERT_OK(err, "bpf_map_delete_elem"))
|
||||
goto close_cli;
|
||||
err = setsockopt(p, IPPROTO_TCP, TCP_ULP, "tls", strlen("tls"));
|
||||
if (!ASSERT_OK(err, "setsockopt(TCP_ULP)"))
|
||||
goto out;
|
||||
|
||||
err = disconnect(cli);
|
||||
memset(&crypto_rx, 0, sizeof(crypto_rx));
|
||||
memset(&crypto_tx, 0, sizeof(crypto_tx));
|
||||
crypto_rx.info.version = TLS_1_2_VERSION;
|
||||
crypto_tx.info.version = TLS_1_2_VERSION;
|
||||
crypto_rx.info.cipher_type = TLS_CIPHER_AES_GCM_128;
|
||||
crypto_tx.info.cipher_type = TLS_CIPHER_AES_GCM_128;
|
||||
|
||||
close_cli:
|
||||
close(cli);
|
||||
close_srv:
|
||||
close(srv);
|
||||
err = setsockopt(c, SOL_TLS, TLS_TX, &crypto_tx, sizeof(crypto_tx));
|
||||
if (!ASSERT_OK(err, "setsockopt(TLS_TX)"))
|
||||
goto out;
|
||||
|
||||
err = setsockopt(p, SOL_TLS, TLS_RX, &crypto_rx, sizeof(crypto_rx));
|
||||
if (!ASSERT_OK(err, "setsockopt(TLS_RX)"))
|
||||
goto out;
|
||||
return 0;
|
||||
out:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int create_ktls_pairs(int family, int sotype, int *c, int *p)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = create_pair(family, sotype, c, p);
|
||||
if (!ASSERT_OK(err, "create_pair()"))
|
||||
return -1;
|
||||
|
||||
err = init_ktls_pairs(*c, *p);
|
||||
if (!ASSERT_OK(err, "init_ktls_pairs(c, p)"))
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_sockmap_ktls_update_fails_when_sock_has_ulp(int family, int map)
|
||||
@@ -145,6 +131,189 @@ static const char *fmt_test_name(const char *subtest_name, int family,
|
||||
return test_name;
|
||||
}
|
||||
|
||||
static void test_sockmap_ktls_offload(int family, int sotype)
|
||||
{
|
||||
int err;
|
||||
int c = 0, p = 0, sent, recvd;
|
||||
char msg[12] = "hello world\0";
|
||||
char rcv[13];
|
||||
|
||||
err = create_ktls_pairs(family, sotype, &c, &p);
|
||||
if (!ASSERT_OK(err, "create_ktls_pairs()"))
|
||||
goto out;
|
||||
|
||||
sent = send(c, msg, sizeof(msg), 0);
|
||||
if (!ASSERT_OK(err, "send(msg)"))
|
||||
goto out;
|
||||
|
||||
recvd = recv(p, rcv, sizeof(rcv), 0);
|
||||
if (!ASSERT_OK(err, "recv(msg)") ||
|
||||
!ASSERT_EQ(recvd, sent, "length mismatch"))
|
||||
goto out;
|
||||
|
||||
ASSERT_OK(memcmp(msg, rcv, sizeof(msg)), "data mismatch");
|
||||
|
||||
out:
|
||||
if (c)
|
||||
close(c);
|
||||
if (p)
|
||||
close(p);
|
||||
}
|
||||
|
||||
static void test_sockmap_ktls_tx_cork(int family, int sotype, bool push)
|
||||
{
|
||||
int err, off;
|
||||
int i, j;
|
||||
int start_push = 0, push_len = 0;
|
||||
int c = 0, p = 0, one = 1, sent, recvd;
|
||||
int prog_fd, map_fd;
|
||||
char msg[12] = "hello world\0";
|
||||
char rcv[20] = {0};
|
||||
struct test_sockmap_ktls *skel;
|
||||
|
||||
skel = test_sockmap_ktls__open_and_load();
|
||||
if (!ASSERT_TRUE(skel, "open ktls skel"))
|
||||
return;
|
||||
|
||||
err = create_pair(family, sotype, &c, &p);
|
||||
if (!ASSERT_OK(err, "create_pair()"))
|
||||
goto out;
|
||||
|
||||
prog_fd = bpf_program__fd(skel->progs.prog_sk_policy);
|
||||
map_fd = bpf_map__fd(skel->maps.sock_map);
|
||||
|
||||
err = bpf_prog_attach(prog_fd, map_fd, BPF_SK_MSG_VERDICT, 0);
|
||||
if (!ASSERT_OK(err, "bpf_prog_attach sk msg"))
|
||||
goto out;
|
||||
|
||||
err = bpf_map_update_elem(map_fd, &one, &c, BPF_NOEXIST);
|
||||
if (!ASSERT_OK(err, "bpf_map_update_elem(c)"))
|
||||
goto out;
|
||||
|
||||
err = init_ktls_pairs(c, p);
|
||||
if (!ASSERT_OK(err, "init_ktls_pairs(c, p)"))
|
||||
goto out;
|
||||
|
||||
skel->bss->cork_byte = sizeof(msg);
|
||||
if (push) {
|
||||
start_push = 1;
|
||||
push_len = 2;
|
||||
}
|
||||
skel->bss->push_start = start_push;
|
||||
skel->bss->push_end = push_len;
|
||||
|
||||
off = sizeof(msg) / 2;
|
||||
sent = send(c, msg, off, 0);
|
||||
if (!ASSERT_EQ(sent, off, "send(msg)"))
|
||||
goto out;
|
||||
|
||||
recvd = recv_timeout(p, rcv, sizeof(rcv), MSG_DONTWAIT, 1);
|
||||
if (!ASSERT_EQ(-1, recvd, "expected no data"))
|
||||
goto out;
|
||||
|
||||
/* send remaining msg */
|
||||
sent = send(c, msg + off, sizeof(msg) - off, 0);
|
||||
if (!ASSERT_EQ(sent, sizeof(msg) - off, "send remaining data"))
|
||||
goto out;
|
||||
|
||||
recvd = recv_timeout(p, rcv, sizeof(rcv), MSG_DONTWAIT, 1);
|
||||
if (!ASSERT_OK(err, "recv(msg)") ||
|
||||
!ASSERT_EQ(recvd, sizeof(msg) + push_len, "check length mismatch"))
|
||||
goto out;
|
||||
|
||||
for (i = 0, j = 0; i < recvd;) {
|
||||
/* skip checking the data that has been pushed in */
|
||||
if (i >= start_push && i <= start_push + push_len - 1) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (!ASSERT_EQ(rcv[i], msg[j], "data mismatch"))
|
||||
goto out;
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
out:
|
||||
if (c)
|
||||
close(c);
|
||||
if (p)
|
||||
close(p);
|
||||
test_sockmap_ktls__destroy(skel);
|
||||
}
|
||||
|
||||
static void test_sockmap_ktls_tx_no_buf(int family, int sotype, bool push)
|
||||
{
|
||||
int c = -1, p = -1, one = 1, two = 2;
|
||||
struct test_sockmap_ktls *skel;
|
||||
unsigned char *data = NULL;
|
||||
struct msghdr msg = {0};
|
||||
struct iovec iov[2];
|
||||
int prog_fd, map_fd;
|
||||
int txrx_buf = 1024;
|
||||
int iov_length = 8192;
|
||||
int err;
|
||||
|
||||
skel = test_sockmap_ktls__open_and_load();
|
||||
if (!ASSERT_TRUE(skel, "open ktls skel"))
|
||||
return;
|
||||
|
||||
err = create_pair(family, sotype, &c, &p);
|
||||
if (!ASSERT_OK(err, "create_pair()"))
|
||||
goto out;
|
||||
|
||||
err = setsockopt(c, SOL_SOCKET, SO_RCVBUFFORCE, &txrx_buf, sizeof(int));
|
||||
err |= setsockopt(p, SOL_SOCKET, SO_SNDBUFFORCE, &txrx_buf, sizeof(int));
|
||||
if (!ASSERT_OK(err, "set buf limit"))
|
||||
goto out;
|
||||
|
||||
prog_fd = bpf_program__fd(skel->progs.prog_sk_policy_redir);
|
||||
map_fd = bpf_map__fd(skel->maps.sock_map);
|
||||
|
||||
err = bpf_prog_attach(prog_fd, map_fd, BPF_SK_MSG_VERDICT, 0);
|
||||
if (!ASSERT_OK(err, "bpf_prog_attach sk msg"))
|
||||
goto out;
|
||||
|
||||
err = bpf_map_update_elem(map_fd, &one, &c, BPF_NOEXIST);
|
||||
if (!ASSERT_OK(err, "bpf_map_update_elem(c)"))
|
||||
goto out;
|
||||
|
||||
err = bpf_map_update_elem(map_fd, &two, &p, BPF_NOEXIST);
|
||||
if (!ASSERT_OK(err, "bpf_map_update_elem(p)"))
|
||||
goto out;
|
||||
|
||||
skel->bss->apply_bytes = 1024;
|
||||
|
||||
err = init_ktls_pairs(c, p);
|
||||
if (!ASSERT_OK(err, "init_ktls_pairs(c, p)"))
|
||||
goto out;
|
||||
|
||||
data = calloc(iov_length, sizeof(char));
|
||||
if (!data)
|
||||
goto out;
|
||||
|
||||
iov[0].iov_base = data;
|
||||
iov[0].iov_len = iov_length;
|
||||
iov[1].iov_base = data;
|
||||
iov[1].iov_len = iov_length;
|
||||
msg.msg_iov = iov;
|
||||
msg.msg_iovlen = 2;
|
||||
|
||||
for (;;) {
|
||||
err = sendmsg(c, &msg, MSG_DONTWAIT);
|
||||
if (err <= 0)
|
||||
break;
|
||||
}
|
||||
|
||||
out:
|
||||
if (data)
|
||||
free(data);
|
||||
if (c != -1)
|
||||
close(c);
|
||||
if (p != -1)
|
||||
close(p);
|
||||
|
||||
test_sockmap_ktls__destroy(skel);
|
||||
}
|
||||
|
||||
static void run_tests(int family, enum bpf_map_type map_type)
|
||||
{
|
||||
int map;
|
||||
@@ -153,18 +322,30 @@ static void run_tests(int family, enum bpf_map_type map_type)
|
||||
if (!ASSERT_GE(map, 0, "bpf_map_create"))
|
||||
return;
|
||||
|
||||
if (test__start_subtest(fmt_test_name("disconnect_after_delete", family, map_type)))
|
||||
test_sockmap_ktls_disconnect_after_delete(family, map);
|
||||
if (test__start_subtest(fmt_test_name("update_fails_when_sock_has_ulp", family, map_type)))
|
||||
test_sockmap_ktls_update_fails_when_sock_has_ulp(family, map);
|
||||
|
||||
close(map);
|
||||
}
|
||||
|
||||
static void run_ktls_test(int family, int sotype)
|
||||
{
|
||||
if (test__start_subtest("tls simple offload"))
|
||||
test_sockmap_ktls_offload(family, sotype);
|
||||
if (test__start_subtest("tls tx cork"))
|
||||
test_sockmap_ktls_tx_cork(family, sotype, false);
|
||||
if (test__start_subtest("tls tx cork with push"))
|
||||
test_sockmap_ktls_tx_cork(family, sotype, true);
|
||||
if (test__start_subtest("tls tx egress with no buf"))
|
||||
test_sockmap_ktls_tx_no_buf(family, sotype, true);
|
||||
}
|
||||
|
||||
void test_sockmap_ktls(void)
|
||||
{
|
||||
run_tests(AF_INET, BPF_MAP_TYPE_SOCKMAP);
|
||||
run_tests(AF_INET, BPF_MAP_TYPE_SOCKHASH);
|
||||
run_tests(AF_INET6, BPF_MAP_TYPE_SOCKMAP);
|
||||
run_tests(AF_INET6, BPF_MAP_TYPE_SOCKHASH);
|
||||
run_ktls_test(AF_INET, SOCK_STREAM);
|
||||
run_ktls_test(AF_INET6, SOCK_STREAM);
|
||||
}
|
||||
|
||||
@@ -1366,237 +1366,6 @@ static void test_redir(struct test_sockmap_listen *skel, struct bpf_map *map,
|
||||
}
|
||||
}
|
||||
|
||||
static void pairs_redir_to_connected(int cli0, int peer0, int cli1, int peer1,
|
||||
int sock_mapfd, int nop_mapfd,
|
||||
int verd_mapfd, enum redir_mode mode,
|
||||
int send_flags)
|
||||
{
|
||||
const char *log_prefix = redir_mode_str(mode);
|
||||
unsigned int pass;
|
||||
int err, n;
|
||||
u32 key;
|
||||
char b;
|
||||
|
||||
zero_verdict_count(verd_mapfd);
|
||||
|
||||
err = add_to_sockmap(sock_mapfd, peer0, peer1);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
if (nop_mapfd >= 0) {
|
||||
err = add_to_sockmap(nop_mapfd, cli0, cli1);
|
||||
if (err)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Last byte is OOB data when send_flags has MSG_OOB bit set */
|
||||
n = xsend(cli1, "ab", 2, send_flags);
|
||||
if (n >= 0 && n < 2)
|
||||
FAIL("%s: incomplete send", log_prefix);
|
||||
if (n < 2)
|
||||
return;
|
||||
|
||||
key = SK_PASS;
|
||||
err = xbpf_map_lookup_elem(verd_mapfd, &key, &pass);
|
||||
if (err)
|
||||
return;
|
||||
if (pass != 1)
|
||||
FAIL("%s: want pass count 1, have %d", log_prefix, pass);
|
||||
|
||||
n = recv_timeout(mode == REDIR_INGRESS ? peer0 : cli0, &b, 1, 0, IO_TIMEOUT_SEC);
|
||||
if (n < 0)
|
||||
FAIL_ERRNO("%s: recv_timeout", log_prefix);
|
||||
if (n == 0)
|
||||
FAIL("%s: incomplete recv", log_prefix);
|
||||
|
||||
if (send_flags & MSG_OOB) {
|
||||
/* Check that we can't read OOB while in sockmap */
|
||||
errno = 0;
|
||||
n = recv(peer1, &b, 1, MSG_OOB | MSG_DONTWAIT);
|
||||
if (n != -1 || errno != EOPNOTSUPP)
|
||||
FAIL("%s: recv(MSG_OOB): expected EOPNOTSUPP: retval=%d errno=%d",
|
||||
log_prefix, n, errno);
|
||||
|
||||
/* Remove peer1 from sockmap */
|
||||
xbpf_map_delete_elem(sock_mapfd, &(int){ 1 });
|
||||
|
||||
/* Check that OOB was dropped on redirect */
|
||||
errno = 0;
|
||||
n = recv(peer1, &b, 1, MSG_OOB | MSG_DONTWAIT);
|
||||
if (n != -1 || errno != EINVAL)
|
||||
FAIL("%s: recv(MSG_OOB): expected EINVAL: retval=%d errno=%d",
|
||||
log_prefix, n, errno);
|
||||
}
|
||||
}
|
||||
|
||||
static void unix_redir_to_connected(int sotype, int sock_mapfd,
|
||||
int verd_mapfd, enum redir_mode mode)
|
||||
{
|
||||
int c0, c1, p0, p1;
|
||||
int sfd[2];
|
||||
|
||||
if (socketpair(AF_UNIX, sotype | SOCK_NONBLOCK, 0, sfd))
|
||||
return;
|
||||
c0 = sfd[0], p0 = sfd[1];
|
||||
|
||||
if (socketpair(AF_UNIX, sotype | SOCK_NONBLOCK, 0, sfd))
|
||||
goto close0;
|
||||
c1 = sfd[0], p1 = sfd[1];
|
||||
|
||||
pairs_redir_to_connected(c0, p0, c1, p1, sock_mapfd, -1, verd_mapfd,
|
||||
mode, NO_FLAGS);
|
||||
|
||||
xclose(c1);
|
||||
xclose(p1);
|
||||
close0:
|
||||
xclose(c0);
|
||||
xclose(p0);
|
||||
}
|
||||
|
||||
static void unix_skb_redir_to_connected(struct test_sockmap_listen *skel,
|
||||
struct bpf_map *inner_map, int sotype)
|
||||
{
|
||||
int verdict = bpf_program__fd(skel->progs.prog_skb_verdict);
|
||||
int verdict_map = bpf_map__fd(skel->maps.verdict_map);
|
||||
int sock_map = bpf_map__fd(inner_map);
|
||||
int err;
|
||||
|
||||
err = xbpf_prog_attach(verdict, sock_map, BPF_SK_SKB_VERDICT, 0);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
skel->bss->test_ingress = false;
|
||||
unix_redir_to_connected(sotype, sock_map, verdict_map, REDIR_EGRESS);
|
||||
skel->bss->test_ingress = true;
|
||||
unix_redir_to_connected(sotype, sock_map, verdict_map, REDIR_INGRESS);
|
||||
|
||||
xbpf_prog_detach2(verdict, sock_map, BPF_SK_SKB_VERDICT);
|
||||
}
|
||||
|
||||
static void test_unix_redir(struct test_sockmap_listen *skel, struct bpf_map *map,
|
||||
int sotype)
|
||||
{
|
||||
const char *family_name, *map_name;
|
||||
char s[MAX_TEST_NAME];
|
||||
|
||||
family_name = family_str(AF_UNIX);
|
||||
map_name = map_type_str(map);
|
||||
snprintf(s, sizeof(s), "%s %s %s", map_name, family_name, __func__);
|
||||
if (!test__start_subtest(s))
|
||||
return;
|
||||
unix_skb_redir_to_connected(skel, map, sotype);
|
||||
}
|
||||
|
||||
/* Returns two connected loopback vsock sockets */
|
||||
static int vsock_socketpair_connectible(int sotype, int *v0, int *v1)
|
||||
{
|
||||
return create_pair(AF_VSOCK, sotype | SOCK_NONBLOCK, v0, v1);
|
||||
}
|
||||
|
||||
static void vsock_unix_redir_connectible(int sock_mapfd, int verd_mapfd,
|
||||
enum redir_mode mode, int sotype)
|
||||
{
|
||||
const char *log_prefix = redir_mode_str(mode);
|
||||
char a = 'a', b = 'b';
|
||||
int u0, u1, v0, v1;
|
||||
int sfd[2];
|
||||
unsigned int pass;
|
||||
int err, n;
|
||||
u32 key;
|
||||
|
||||
zero_verdict_count(verd_mapfd);
|
||||
|
||||
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, sfd))
|
||||
return;
|
||||
|
||||
u0 = sfd[0];
|
||||
u1 = sfd[1];
|
||||
|
||||
err = vsock_socketpair_connectible(sotype, &v0, &v1);
|
||||
if (err) {
|
||||
FAIL("vsock_socketpair_connectible() failed");
|
||||
goto close_uds;
|
||||
}
|
||||
|
||||
err = add_to_sockmap(sock_mapfd, u0, v0);
|
||||
if (err) {
|
||||
FAIL("add_to_sockmap failed");
|
||||
goto close_vsock;
|
||||
}
|
||||
|
||||
n = write(v1, &a, sizeof(a));
|
||||
if (n < 0)
|
||||
FAIL_ERRNO("%s: write", log_prefix);
|
||||
if (n == 0)
|
||||
FAIL("%s: incomplete write", log_prefix);
|
||||
if (n < 1)
|
||||
goto out;
|
||||
|
||||
n = xrecv_nonblock(mode == REDIR_INGRESS ? u0 : u1, &b, sizeof(b), 0);
|
||||
if (n < 0)
|
||||
FAIL("%s: recv() err, errno=%d", log_prefix, errno);
|
||||
if (n == 0)
|
||||
FAIL("%s: incomplete recv", log_prefix);
|
||||
if (b != a)
|
||||
FAIL("%s: vsock socket map failed, %c != %c", log_prefix, a, b);
|
||||
|
||||
key = SK_PASS;
|
||||
err = xbpf_map_lookup_elem(verd_mapfd, &key, &pass);
|
||||
if (err)
|
||||
goto out;
|
||||
if (pass != 1)
|
||||
FAIL("%s: want pass count 1, have %d", log_prefix, pass);
|
||||
out:
|
||||
key = 0;
|
||||
bpf_map_delete_elem(sock_mapfd, &key);
|
||||
key = 1;
|
||||
bpf_map_delete_elem(sock_mapfd, &key);
|
||||
|
||||
close_vsock:
|
||||
close(v0);
|
||||
close(v1);
|
||||
|
||||
close_uds:
|
||||
close(u0);
|
||||
close(u1);
|
||||
}
|
||||
|
||||
static void vsock_unix_skb_redir_connectible(struct test_sockmap_listen *skel,
|
||||
struct bpf_map *inner_map,
|
||||
int sotype)
|
||||
{
|
||||
int verdict = bpf_program__fd(skel->progs.prog_skb_verdict);
|
||||
int verdict_map = bpf_map__fd(skel->maps.verdict_map);
|
||||
int sock_map = bpf_map__fd(inner_map);
|
||||
int err;
|
||||
|
||||
err = xbpf_prog_attach(verdict, sock_map, BPF_SK_SKB_VERDICT, 0);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
skel->bss->test_ingress = false;
|
||||
vsock_unix_redir_connectible(sock_map, verdict_map, REDIR_EGRESS, sotype);
|
||||
skel->bss->test_ingress = true;
|
||||
vsock_unix_redir_connectible(sock_map, verdict_map, REDIR_INGRESS, sotype);
|
||||
|
||||
xbpf_prog_detach2(verdict, sock_map, BPF_SK_SKB_VERDICT);
|
||||
}
|
||||
|
||||
static void test_vsock_redir(struct test_sockmap_listen *skel, struct bpf_map *map)
|
||||
{
|
||||
const char *family_name, *map_name;
|
||||
char s[MAX_TEST_NAME];
|
||||
|
||||
family_name = family_str(AF_VSOCK);
|
||||
map_name = map_type_str(map);
|
||||
snprintf(s, sizeof(s), "%s %s %s", map_name, family_name, __func__);
|
||||
if (!test__start_subtest(s))
|
||||
return;
|
||||
|
||||
vsock_unix_skb_redir_connectible(skel, map, SOCK_STREAM);
|
||||
vsock_unix_skb_redir_connectible(skel, map, SOCK_SEQPACKET);
|
||||
}
|
||||
|
||||
static void test_reuseport(struct test_sockmap_listen *skel,
|
||||
struct bpf_map *map, int family, int sotype)
|
||||
{
|
||||
@@ -1637,224 +1406,6 @@ static void test_reuseport(struct test_sockmap_listen *skel,
|
||||
}
|
||||
}
|
||||
|
||||
static int inet_socketpair(int family, int type, int *s, int *c)
|
||||
{
|
||||
return create_pair(family, type | SOCK_NONBLOCK, s, c);
|
||||
}
|
||||
|
||||
static void udp_redir_to_connected(int family, int sock_mapfd, int verd_mapfd,
|
||||
enum redir_mode mode)
|
||||
{
|
||||
int c0, c1, p0, p1;
|
||||
int err;
|
||||
|
||||
err = inet_socketpair(family, SOCK_DGRAM, &p0, &c0);
|
||||
if (err)
|
||||
return;
|
||||
err = inet_socketpair(family, SOCK_DGRAM, &p1, &c1);
|
||||
if (err)
|
||||
goto close_cli0;
|
||||
|
||||
pairs_redir_to_connected(c0, p0, c1, p1, sock_mapfd, -1, verd_mapfd,
|
||||
mode, NO_FLAGS);
|
||||
|
||||
xclose(c1);
|
||||
xclose(p1);
|
||||
close_cli0:
|
||||
xclose(c0);
|
||||
xclose(p0);
|
||||
}
|
||||
|
||||
static void udp_skb_redir_to_connected(struct test_sockmap_listen *skel,
|
||||
struct bpf_map *inner_map, int family)
|
||||
{
|
||||
int verdict = bpf_program__fd(skel->progs.prog_skb_verdict);
|
||||
int verdict_map = bpf_map__fd(skel->maps.verdict_map);
|
||||
int sock_map = bpf_map__fd(inner_map);
|
||||
int err;
|
||||
|
||||
err = xbpf_prog_attach(verdict, sock_map, BPF_SK_SKB_VERDICT, 0);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
skel->bss->test_ingress = false;
|
||||
udp_redir_to_connected(family, sock_map, verdict_map, REDIR_EGRESS);
|
||||
skel->bss->test_ingress = true;
|
||||
udp_redir_to_connected(family, sock_map, verdict_map, REDIR_INGRESS);
|
||||
|
||||
xbpf_prog_detach2(verdict, sock_map, BPF_SK_SKB_VERDICT);
|
||||
}
|
||||
|
||||
static void test_udp_redir(struct test_sockmap_listen *skel, struct bpf_map *map,
|
||||
int family)
|
||||
{
|
||||
const char *family_name, *map_name;
|
||||
char s[MAX_TEST_NAME];
|
||||
|
||||
family_name = family_str(family);
|
||||
map_name = map_type_str(map);
|
||||
snprintf(s, sizeof(s), "%s %s %s", map_name, family_name, __func__);
|
||||
if (!test__start_subtest(s))
|
||||
return;
|
||||
udp_skb_redir_to_connected(skel, map, family);
|
||||
}
|
||||
|
||||
static void inet_unix_redir_to_connected(int family, int type, int sock_mapfd,
|
||||
int verd_mapfd, enum redir_mode mode)
|
||||
{
|
||||
int c0, c1, p0, p1;
|
||||
int sfd[2];
|
||||
int err;
|
||||
|
||||
if (socketpair(AF_UNIX, type | SOCK_NONBLOCK, 0, sfd))
|
||||
return;
|
||||
c0 = sfd[0], p0 = sfd[1];
|
||||
|
||||
err = inet_socketpair(family, type, &p1, &c1);
|
||||
if (err)
|
||||
goto close;
|
||||
|
||||
pairs_redir_to_connected(c0, p0, c1, p1, sock_mapfd, -1, verd_mapfd,
|
||||
mode, NO_FLAGS);
|
||||
|
||||
xclose(c1);
|
||||
xclose(p1);
|
||||
close:
|
||||
xclose(c0);
|
||||
xclose(p0);
|
||||
}
|
||||
|
||||
static void inet_unix_skb_redir_to_connected(struct test_sockmap_listen *skel,
|
||||
struct bpf_map *inner_map, int family)
|
||||
{
|
||||
int verdict = bpf_program__fd(skel->progs.prog_skb_verdict);
|
||||
int verdict_map = bpf_map__fd(skel->maps.verdict_map);
|
||||
int sock_map = bpf_map__fd(inner_map);
|
||||
int err;
|
||||
|
||||
err = xbpf_prog_attach(verdict, sock_map, BPF_SK_SKB_VERDICT, 0);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
skel->bss->test_ingress = false;
|
||||
inet_unix_redir_to_connected(family, SOCK_DGRAM, sock_map, verdict_map,
|
||||
REDIR_EGRESS);
|
||||
inet_unix_redir_to_connected(family, SOCK_STREAM, sock_map, verdict_map,
|
||||
REDIR_EGRESS);
|
||||
skel->bss->test_ingress = true;
|
||||
inet_unix_redir_to_connected(family, SOCK_DGRAM, sock_map, verdict_map,
|
||||
REDIR_INGRESS);
|
||||
inet_unix_redir_to_connected(family, SOCK_STREAM, sock_map, verdict_map,
|
||||
REDIR_INGRESS);
|
||||
|
||||
xbpf_prog_detach2(verdict, sock_map, BPF_SK_SKB_VERDICT);
|
||||
}
|
||||
|
||||
static void unix_inet_redir_to_connected(int family, int type, int sock_mapfd,
|
||||
int nop_mapfd, int verd_mapfd,
|
||||
enum redir_mode mode, int send_flags)
|
||||
{
|
||||
int c0, c1, p0, p1;
|
||||
int sfd[2];
|
||||
int err;
|
||||
|
||||
err = inet_socketpair(family, type, &p0, &c0);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
if (socketpair(AF_UNIX, type | SOCK_NONBLOCK, 0, sfd))
|
||||
goto close_cli0;
|
||||
c1 = sfd[0], p1 = sfd[1];
|
||||
|
||||
pairs_redir_to_connected(c0, p0, c1, p1, sock_mapfd, nop_mapfd,
|
||||
verd_mapfd, mode, send_flags);
|
||||
|
||||
xclose(c1);
|
||||
xclose(p1);
|
||||
close_cli0:
|
||||
xclose(c0);
|
||||
xclose(p0);
|
||||
}
|
||||
|
||||
static void unix_inet_skb_redir_to_connected(struct test_sockmap_listen *skel,
|
||||
struct bpf_map *inner_map, int family)
|
||||
{
|
||||
int verdict = bpf_program__fd(skel->progs.prog_skb_verdict);
|
||||
int nop_map = bpf_map__fd(skel->maps.nop_map);
|
||||
int verdict_map = bpf_map__fd(skel->maps.verdict_map);
|
||||
int sock_map = bpf_map__fd(inner_map);
|
||||
int err;
|
||||
|
||||
err = xbpf_prog_attach(verdict, sock_map, BPF_SK_SKB_VERDICT, 0);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
skel->bss->test_ingress = false;
|
||||
unix_inet_redir_to_connected(family, SOCK_DGRAM,
|
||||
sock_map, -1, verdict_map,
|
||||
REDIR_EGRESS, NO_FLAGS);
|
||||
unix_inet_redir_to_connected(family, SOCK_STREAM,
|
||||
sock_map, -1, verdict_map,
|
||||
REDIR_EGRESS, NO_FLAGS);
|
||||
|
||||
unix_inet_redir_to_connected(family, SOCK_DGRAM,
|
||||
sock_map, nop_map, verdict_map,
|
||||
REDIR_EGRESS, NO_FLAGS);
|
||||
unix_inet_redir_to_connected(family, SOCK_STREAM,
|
||||
sock_map, nop_map, verdict_map,
|
||||
REDIR_EGRESS, NO_FLAGS);
|
||||
|
||||
/* MSG_OOB not supported by AF_UNIX SOCK_DGRAM */
|
||||
unix_inet_redir_to_connected(family, SOCK_STREAM,
|
||||
sock_map, nop_map, verdict_map,
|
||||
REDIR_EGRESS, MSG_OOB);
|
||||
|
||||
skel->bss->test_ingress = true;
|
||||
unix_inet_redir_to_connected(family, SOCK_DGRAM,
|
||||
sock_map, -1, verdict_map,
|
||||
REDIR_INGRESS, NO_FLAGS);
|
||||
unix_inet_redir_to_connected(family, SOCK_STREAM,
|
||||
sock_map, -1, verdict_map,
|
||||
REDIR_INGRESS, NO_FLAGS);
|
||||
|
||||
unix_inet_redir_to_connected(family, SOCK_DGRAM,
|
||||
sock_map, nop_map, verdict_map,
|
||||
REDIR_INGRESS, NO_FLAGS);
|
||||
unix_inet_redir_to_connected(family, SOCK_STREAM,
|
||||
sock_map, nop_map, verdict_map,
|
||||
REDIR_INGRESS, NO_FLAGS);
|
||||
|
||||
/* MSG_OOB not supported by AF_UNIX SOCK_DGRAM */
|
||||
unix_inet_redir_to_connected(family, SOCK_STREAM,
|
||||
sock_map, nop_map, verdict_map,
|
||||
REDIR_INGRESS, MSG_OOB);
|
||||
|
||||
xbpf_prog_detach2(verdict, sock_map, BPF_SK_SKB_VERDICT);
|
||||
}
|
||||
|
||||
static void test_udp_unix_redir(struct test_sockmap_listen *skel, struct bpf_map *map,
|
||||
int family)
|
||||
{
|
||||
const char *family_name, *map_name;
|
||||
struct netns_obj *netns;
|
||||
char s[MAX_TEST_NAME];
|
||||
|
||||
family_name = family_str(family);
|
||||
map_name = map_type_str(map);
|
||||
snprintf(s, sizeof(s), "%s %s %s", map_name, family_name, __func__);
|
||||
if (!test__start_subtest(s))
|
||||
return;
|
||||
|
||||
netns = netns_new("sockmap_listen", true);
|
||||
if (!ASSERT_OK_PTR(netns, "netns_new"))
|
||||
return;
|
||||
|
||||
inet_unix_skb_redir_to_connected(skel, map, family);
|
||||
unix_inet_skb_redir_to_connected(skel, map, family);
|
||||
|
||||
netns_free(netns);
|
||||
}
|
||||
|
||||
static void run_tests(struct test_sockmap_listen *skel, struct bpf_map *map,
|
||||
int family)
|
||||
{
|
||||
@@ -1863,8 +1414,6 @@ static void run_tests(struct test_sockmap_listen *skel, struct bpf_map *map,
|
||||
test_redir(skel, map, family, SOCK_STREAM);
|
||||
test_reuseport(skel, map, family, SOCK_STREAM);
|
||||
test_reuseport(skel, map, family, SOCK_DGRAM);
|
||||
test_udp_redir(skel, map, family);
|
||||
test_udp_unix_redir(skel, map, family);
|
||||
}
|
||||
|
||||
void serial_test_sockmap_listen(void)
|
||||
@@ -1880,16 +1429,10 @@ void serial_test_sockmap_listen(void)
|
||||
skel->bss->test_sockmap = true;
|
||||
run_tests(skel, skel->maps.sock_map, AF_INET);
|
||||
run_tests(skel, skel->maps.sock_map, AF_INET6);
|
||||
test_unix_redir(skel, skel->maps.sock_map, SOCK_DGRAM);
|
||||
test_unix_redir(skel, skel->maps.sock_map, SOCK_STREAM);
|
||||
test_vsock_redir(skel, skel->maps.sock_map);
|
||||
|
||||
skel->bss->test_sockmap = false;
|
||||
run_tests(skel, skel->maps.sock_hash, AF_INET);
|
||||
run_tests(skel, skel->maps.sock_hash, AF_INET6);
|
||||
test_unix_redir(skel, skel->maps.sock_hash, SOCK_DGRAM);
|
||||
test_unix_redir(skel, skel->maps.sock_hash, SOCK_STREAM);
|
||||
test_vsock_redir(skel, skel->maps.sock_hash);
|
||||
|
||||
test_sockmap_listen__destroy(skel);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,465 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Test for sockmap/sockhash redirection.
|
||||
*
|
||||
* BPF_MAP_TYPE_SOCKMAP
|
||||
* BPF_MAP_TYPE_SOCKHASH
|
||||
* x
|
||||
* sk_msg-to-egress
|
||||
* sk_msg-to-ingress
|
||||
* sk_skb-to-egress
|
||||
* sk_skb-to-ingress
|
||||
* x
|
||||
* AF_INET, SOCK_STREAM
|
||||
* AF_INET6, SOCK_STREAM
|
||||
* AF_INET, SOCK_DGRAM
|
||||
* AF_INET6, SOCK_DGRAM
|
||||
* AF_UNIX, SOCK_STREAM
|
||||
* AF_UNIX, SOCK_DGRAM
|
||||
* AF_VSOCK, SOCK_STREAM
|
||||
* AF_VSOCK, SOCK_SEQPACKET
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <error.h>
|
||||
#include <sched.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/vm_sockets.h>
|
||||
|
||||
#include <bpf/bpf.h>
|
||||
#include <bpf/libbpf.h>
|
||||
|
||||
#include "linux/const.h"
|
||||
#include "test_progs.h"
|
||||
#include "sockmap_helpers.h"
|
||||
#include "test_sockmap_redir.skel.h"
|
||||
|
||||
/* The meaning of SUPPORTED is "will redirect packet as expected".
|
||||
*/
|
||||
#define SUPPORTED _BITUL(0)
|
||||
|
||||
/* Note on sk_skb-to-ingress ->af_vsock:
|
||||
*
|
||||
* Peer socket may receive the packet some time after the return from sendmsg().
|
||||
* In a typical usage scenario, recvmsg() will block until the redirected packet
|
||||
* appears in the destination queue, or timeout if the packet was dropped. By
|
||||
* that point, the verdict map has already been updated to reflect what has
|
||||
* happened.
|
||||
*
|
||||
* But sk_skb-to-ingress/af_vsock is an unsupported combination, so no recvmsg()
|
||||
* takes place. Which means we may race the execution of the verdict logic and
|
||||
* read map_verd before it has been updated, i.e. we might observe
|
||||
* map_verd[SK_DROP]=0 instead of map_verd[SK_DROP]=1.
|
||||
*
|
||||
* This confuses the selftest logic: if there was no packet dropped, where's the
|
||||
* packet? So here's a heuristic: on map_verd[SK_DROP]=map_verd[SK_PASS]=0
|
||||
* (which implies the verdict program has not been ran) just re-read the verdict
|
||||
* map again.
|
||||
*/
|
||||
#define UNSUPPORTED_RACY_VERD _BITUL(1)
|
||||
|
||||
enum prog_type {
|
||||
SK_MSG_EGRESS,
|
||||
SK_MSG_INGRESS,
|
||||
SK_SKB_EGRESS,
|
||||
SK_SKB_INGRESS,
|
||||
};
|
||||
|
||||
enum {
|
||||
SEND_INNER = 0,
|
||||
SEND_OUTER,
|
||||
};
|
||||
|
||||
enum {
|
||||
RECV_INNER = 0,
|
||||
RECV_OUTER,
|
||||
};
|
||||
|
||||
struct maps {
|
||||
int in;
|
||||
int out;
|
||||
int verd;
|
||||
};
|
||||
|
||||
struct combo_spec {
|
||||
enum prog_type prog_type;
|
||||
const char *in, *out;
|
||||
};
|
||||
|
||||
struct redir_spec {
|
||||
const char *name;
|
||||
int idx_send;
|
||||
int idx_recv;
|
||||
enum prog_type prog_type;
|
||||
};
|
||||
|
||||
struct socket_spec {
|
||||
int family;
|
||||
int sotype;
|
||||
int send_flags;
|
||||
int in[2];
|
||||
int out[2];
|
||||
};
|
||||
|
||||
static int socket_spec_pairs(struct socket_spec *s)
|
||||
{
|
||||
return create_socket_pairs(s->family, s->sotype,
|
||||
&s->in[0], &s->out[0],
|
||||
&s->in[1], &s->out[1]);
|
||||
}
|
||||
|
||||
static void socket_spec_close(struct socket_spec *s)
|
||||
{
|
||||
xclose(s->in[0]);
|
||||
xclose(s->in[1]);
|
||||
xclose(s->out[0]);
|
||||
xclose(s->out[1]);
|
||||
}
|
||||
|
||||
static void get_redir_params(struct redir_spec *redir,
|
||||
struct test_sockmap_redir *skel, int *prog_fd,
|
||||
enum bpf_attach_type *attach_type,
|
||||
int *redirect_flags)
|
||||
{
|
||||
enum prog_type type = redir->prog_type;
|
||||
struct bpf_program *prog;
|
||||
bool sk_msg;
|
||||
|
||||
sk_msg = type == SK_MSG_INGRESS || type == SK_MSG_EGRESS;
|
||||
prog = sk_msg ? skel->progs.prog_msg_verdict : skel->progs.prog_skb_verdict;
|
||||
|
||||
*prog_fd = bpf_program__fd(prog);
|
||||
*attach_type = sk_msg ? BPF_SK_MSG_VERDICT : BPF_SK_SKB_VERDICT;
|
||||
|
||||
if (type == SK_MSG_INGRESS || type == SK_SKB_INGRESS)
|
||||
*redirect_flags = BPF_F_INGRESS;
|
||||
else
|
||||
*redirect_flags = 0;
|
||||
}
|
||||
|
||||
static void try_recv(const char *prefix, int fd, int flags, bool expect_success)
|
||||
{
|
||||
ssize_t n;
|
||||
char buf;
|
||||
|
||||
errno = 0;
|
||||
n = recv(fd, &buf, 1, flags);
|
||||
if (n < 0 && expect_success)
|
||||
FAIL_ERRNO("%s: unexpected failure: retval=%zd", prefix, n);
|
||||
if (!n && !expect_success)
|
||||
FAIL("%s: expected failure: retval=%zd", prefix, n);
|
||||
}
|
||||
|
||||
static void handle_unsupported(int sd_send, int sd_peer, int sd_in, int sd_out,
|
||||
int sd_recv, int map_verd, int status)
|
||||
{
|
||||
unsigned int drop, pass;
|
||||
char recv_buf;
|
||||
ssize_t n;
|
||||
|
||||
get_verdict:
|
||||
if (xbpf_map_lookup_elem(map_verd, &u32(SK_DROP), &drop) ||
|
||||
xbpf_map_lookup_elem(map_verd, &u32(SK_PASS), &pass))
|
||||
return;
|
||||
|
||||
if (pass == 0 && drop == 0 && (status & UNSUPPORTED_RACY_VERD)) {
|
||||
sched_yield();
|
||||
goto get_verdict;
|
||||
}
|
||||
|
||||
if (pass != 0) {
|
||||
FAIL("unsupported: wanted verdict pass 0, have %u", pass);
|
||||
return;
|
||||
}
|
||||
|
||||
/* If nothing was dropped, packet should have reached the peer */
|
||||
if (drop == 0) {
|
||||
errno = 0;
|
||||
n = recv_timeout(sd_peer, &recv_buf, 1, 0, IO_TIMEOUT_SEC);
|
||||
if (n != 1)
|
||||
FAIL_ERRNO("unsupported: packet missing, retval=%zd", n);
|
||||
}
|
||||
|
||||
/* Ensure queues are empty */
|
||||
try_recv("bpf.recv(sd_send)", sd_send, MSG_DONTWAIT, false);
|
||||
if (sd_in != sd_send)
|
||||
try_recv("bpf.recv(sd_in)", sd_in, MSG_DONTWAIT, false);
|
||||
|
||||
try_recv("bpf.recv(sd_out)", sd_out, MSG_DONTWAIT, false);
|
||||
if (sd_recv != sd_out)
|
||||
try_recv("bpf.recv(sd_recv)", sd_recv, MSG_DONTWAIT, false);
|
||||
}
|
||||
|
||||
static void test_send_redir_recv(int sd_send, int send_flags, int sd_peer,
|
||||
int sd_in, int sd_out, int sd_recv,
|
||||
struct maps *maps, int status)
|
||||
{
|
||||
unsigned int drop, pass;
|
||||
char *send_buf = "ab";
|
||||
char recv_buf = '\0';
|
||||
ssize_t n, len = 1;
|
||||
|
||||
/* Zero out the verdict map */
|
||||
if (xbpf_map_update_elem(maps->verd, &u32(SK_DROP), &u32(0), BPF_ANY) ||
|
||||
xbpf_map_update_elem(maps->verd, &u32(SK_PASS), &u32(0), BPF_ANY))
|
||||
return;
|
||||
|
||||
if (xbpf_map_update_elem(maps->in, &u32(0), &u64(sd_in), BPF_NOEXIST))
|
||||
return;
|
||||
|
||||
if (xbpf_map_update_elem(maps->out, &u32(0), &u64(sd_out), BPF_NOEXIST))
|
||||
goto del_in;
|
||||
|
||||
/* Last byte is OOB data when send_flags has MSG_OOB bit set */
|
||||
if (send_flags & MSG_OOB)
|
||||
len++;
|
||||
n = send(sd_send, send_buf, len, send_flags);
|
||||
if (n >= 0 && n < len)
|
||||
FAIL("incomplete send");
|
||||
if (n < 0) {
|
||||
/* sk_msg redirect combo not supported? */
|
||||
if (status & SUPPORTED || errno != EACCES)
|
||||
FAIL_ERRNO("send");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!(status & SUPPORTED)) {
|
||||
handle_unsupported(sd_send, sd_peer, sd_in, sd_out, sd_recv,
|
||||
maps->verd, status);
|
||||
goto out;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
n = recv_timeout(sd_recv, &recv_buf, 1, 0, IO_TIMEOUT_SEC);
|
||||
if (n != 1) {
|
||||
FAIL_ERRNO("recv_timeout()");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Check verdict _after_ recv(); af_vsock may need time to catch up */
|
||||
if (xbpf_map_lookup_elem(maps->verd, &u32(SK_DROP), &drop) ||
|
||||
xbpf_map_lookup_elem(maps->verd, &u32(SK_PASS), &pass))
|
||||
goto out;
|
||||
|
||||
if (drop != 0 || pass != 1)
|
||||
FAIL("unexpected verdict drop/pass: wanted 0/1, have %u/%u",
|
||||
drop, pass);
|
||||
|
||||
if (recv_buf != send_buf[0])
|
||||
FAIL("recv(): payload check, %02x != %02x", recv_buf, send_buf[0]);
|
||||
|
||||
if (send_flags & MSG_OOB) {
|
||||
/* Fail reading OOB while in sockmap */
|
||||
try_recv("bpf.recv(sd_out, MSG_OOB)", sd_out,
|
||||
MSG_OOB | MSG_DONTWAIT, false);
|
||||
|
||||
/* Remove sd_out from sockmap */
|
||||
xbpf_map_delete_elem(maps->out, &u32(0));
|
||||
|
||||
/* Check that OOB was dropped on redirect */
|
||||
try_recv("recv(sd_out, MSG_OOB)", sd_out,
|
||||
MSG_OOB | MSG_DONTWAIT, false);
|
||||
|
||||
goto del_in;
|
||||
}
|
||||
out:
|
||||
xbpf_map_delete_elem(maps->out, &u32(0));
|
||||
del_in:
|
||||
xbpf_map_delete_elem(maps->in, &u32(0));
|
||||
}
|
||||
|
||||
static int is_redir_supported(enum prog_type type, const char *in,
|
||||
const char *out)
|
||||
{
|
||||
/* Matching based on strings returned by socket_kind_to_str():
|
||||
* tcp4, udp4, tcp6, udp6, u_str, u_dgr, v_str, v_seq
|
||||
* Plus a wildcard: any
|
||||
* Not in use: u_seq, v_dgr
|
||||
*/
|
||||
struct combo_spec *c, combos[] = {
|
||||
/* Send to local: TCP -> any, but vsock */
|
||||
{ SK_MSG_INGRESS, "tcp", "tcp" },
|
||||
{ SK_MSG_INGRESS, "tcp", "udp" },
|
||||
{ SK_MSG_INGRESS, "tcp", "u_str" },
|
||||
{ SK_MSG_INGRESS, "tcp", "u_dgr" },
|
||||
|
||||
/* Send to egress: TCP -> TCP */
|
||||
{ SK_MSG_EGRESS, "tcp", "tcp" },
|
||||
|
||||
/* Ingress to egress: any -> any */
|
||||
{ SK_SKB_EGRESS, "any", "any" },
|
||||
|
||||
/* Ingress to local: any -> any, but vsock */
|
||||
{ SK_SKB_INGRESS, "any", "tcp" },
|
||||
{ SK_SKB_INGRESS, "any", "udp" },
|
||||
{ SK_SKB_INGRESS, "any", "u_str" },
|
||||
{ SK_SKB_INGRESS, "any", "u_dgr" },
|
||||
};
|
||||
|
||||
for (c = combos; c < combos + ARRAY_SIZE(combos); c++) {
|
||||
if (c->prog_type == type &&
|
||||
(!strcmp(c->in, "any") || strstarts(in, c->in)) &&
|
||||
(!strcmp(c->out, "any") || strstarts(out, c->out)))
|
||||
return SUPPORTED;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_support_status(enum prog_type type, const char *in,
|
||||
const char *out)
|
||||
{
|
||||
int status = is_redir_supported(type, in, out);
|
||||
|
||||
if (type == SK_SKB_INGRESS && strstarts(out, "v_"))
|
||||
status |= UNSUPPORTED_RACY_VERD;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static void test_socket(enum bpf_map_type type, struct redir_spec *redir,
|
||||
struct maps *maps, struct socket_spec *s_in,
|
||||
struct socket_spec *s_out)
|
||||
{
|
||||
int fd_in, fd_out, fd_send, fd_peer, fd_recv, flags, status;
|
||||
const char *in_str, *out_str;
|
||||
char s[MAX_TEST_NAME];
|
||||
|
||||
fd_in = s_in->in[0];
|
||||
fd_out = s_out->out[0];
|
||||
fd_send = s_in->in[redir->idx_send];
|
||||
fd_peer = s_in->in[redir->idx_send ^ 1];
|
||||
fd_recv = s_out->out[redir->idx_recv];
|
||||
flags = s_in->send_flags;
|
||||
|
||||
in_str = socket_kind_to_str(fd_in);
|
||||
out_str = socket_kind_to_str(fd_out);
|
||||
status = get_support_status(redir->prog_type, in_str, out_str);
|
||||
|
||||
snprintf(s, sizeof(s),
|
||||
"%-4s %-17s %-5s %s %-5s%6s",
|
||||
/* hash sk_skb-to-ingress u_str → v_str (OOB) */
|
||||
type == BPF_MAP_TYPE_SOCKMAP ? "map" : "hash",
|
||||
redir->name,
|
||||
in_str,
|
||||
status & SUPPORTED ? "→" : " ",
|
||||
out_str,
|
||||
(flags & MSG_OOB) ? "(OOB)" : "");
|
||||
|
||||
if (!test__start_subtest(s))
|
||||
return;
|
||||
|
||||
test_send_redir_recv(fd_send, flags, fd_peer, fd_in, fd_out, fd_recv,
|
||||
maps, status);
|
||||
}
|
||||
|
||||
static void test_redir(enum bpf_map_type type, struct redir_spec *redir,
|
||||
struct maps *maps)
|
||||
{
|
||||
struct socket_spec *s, sockets[] = {
|
||||
{ AF_INET, SOCK_STREAM },
|
||||
// { AF_INET, SOCK_STREAM, MSG_OOB }, /* Known to be broken */
|
||||
{ AF_INET6, SOCK_STREAM },
|
||||
{ AF_INET, SOCK_DGRAM },
|
||||
{ AF_INET6, SOCK_DGRAM },
|
||||
{ AF_UNIX, SOCK_STREAM },
|
||||
{ AF_UNIX, SOCK_STREAM, MSG_OOB },
|
||||
{ AF_UNIX, SOCK_DGRAM },
|
||||
// { AF_UNIX, SOCK_SEQPACKET}, /* Unsupported BPF_MAP_UPDATE_ELEM */
|
||||
{ AF_VSOCK, SOCK_STREAM },
|
||||
// { AF_VSOCK, SOCK_DGRAM }, /* Unsupported socket() */
|
||||
{ AF_VSOCK, SOCK_SEQPACKET },
|
||||
};
|
||||
|
||||
for (s = sockets; s < sockets + ARRAY_SIZE(sockets); s++)
|
||||
if (socket_spec_pairs(s))
|
||||
goto out;
|
||||
|
||||
/* Intra-proto */
|
||||
for (s = sockets; s < sockets + ARRAY_SIZE(sockets); s++)
|
||||
test_socket(type, redir, maps, s, s);
|
||||
|
||||
/* Cross-proto */
|
||||
for (int i = 0; i < ARRAY_SIZE(sockets); i++) {
|
||||
for (int j = 0; j < ARRAY_SIZE(sockets); j++) {
|
||||
struct socket_spec *out = &sockets[j];
|
||||
struct socket_spec *in = &sockets[i];
|
||||
|
||||
/* Skip intra-proto and between variants */
|
||||
if (out->send_flags ||
|
||||
(in->family == out->family &&
|
||||
in->sotype == out->sotype))
|
||||
continue;
|
||||
|
||||
test_socket(type, redir, maps, in, out);
|
||||
}
|
||||
}
|
||||
out:
|
||||
while (--s >= sockets)
|
||||
socket_spec_close(s);
|
||||
}
|
||||
|
||||
static void test_map(enum bpf_map_type type)
|
||||
{
|
||||
struct redir_spec *r, redirs[] = {
|
||||
{ "sk_msg-to-ingress", SEND_INNER, RECV_INNER, SK_MSG_INGRESS },
|
||||
{ "sk_msg-to-egress", SEND_INNER, RECV_OUTER, SK_MSG_EGRESS },
|
||||
{ "sk_skb-to-egress", SEND_OUTER, RECV_OUTER, SK_SKB_EGRESS },
|
||||
{ "sk_skb-to-ingress", SEND_OUTER, RECV_INNER, SK_SKB_INGRESS },
|
||||
};
|
||||
|
||||
for (r = redirs; r < redirs + ARRAY_SIZE(redirs); r++) {
|
||||
enum bpf_attach_type attach_type;
|
||||
struct test_sockmap_redir *skel;
|
||||
struct maps maps;
|
||||
int prog_fd;
|
||||
|
||||
skel = test_sockmap_redir__open_and_load();
|
||||
if (!skel) {
|
||||
FAIL("open_and_load");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case BPF_MAP_TYPE_SOCKMAP:
|
||||
maps.in = bpf_map__fd(skel->maps.nop_map);
|
||||
maps.out = bpf_map__fd(skel->maps.sock_map);
|
||||
break;
|
||||
case BPF_MAP_TYPE_SOCKHASH:
|
||||
maps.in = bpf_map__fd(skel->maps.nop_hash);
|
||||
maps.out = bpf_map__fd(skel->maps.sock_hash);
|
||||
break;
|
||||
default:
|
||||
FAIL("Unsupported bpf_map_type");
|
||||
return;
|
||||
}
|
||||
|
||||
skel->bss->redirect_type = type;
|
||||
maps.verd = bpf_map__fd(skel->maps.verdict_map);
|
||||
get_redir_params(r, skel, &prog_fd, &attach_type,
|
||||
&skel->bss->redirect_flags);
|
||||
|
||||
if (xbpf_prog_attach(prog_fd, maps.in, attach_type, 0))
|
||||
return;
|
||||
|
||||
test_redir(type, r, &maps);
|
||||
|
||||
if (xbpf_prog_detach2(prog_fd, maps.in, attach_type))
|
||||
return;
|
||||
|
||||
test_sockmap_redir__destroy(skel);
|
||||
}
|
||||
}
|
||||
|
||||
void serial_test_sockmap_redir(void)
|
||||
{
|
||||
test_map(BPF_MAP_TYPE_SOCKMAP);
|
||||
test_map(BPF_MAP_TYPE_SOCKHASH);
|
||||
}
|
||||
@@ -56,6 +56,8 @@
|
||||
|
||||
#define MAC_DST_FWD "00:11:22:33:44:55"
|
||||
#define MAC_DST "00:22:33:44:55:66"
|
||||
#define MAC_SRC_FWD "00:33:44:55:66:77"
|
||||
#define MAC_SRC "00:44:55:66:77:88"
|
||||
|
||||
#define IFADDR_STR_LEN 18
|
||||
#define PING_ARGS "-i 0.2 -c 3 -w 10 -q"
|
||||
@@ -207,11 +209,10 @@ static int netns_setup_links_and_routes(struct netns_setup_result *result)
|
||||
int err;
|
||||
|
||||
if (result->dev_mode == MODE_VETH) {
|
||||
SYS(fail, "ip link add src type veth peer name src_fwd");
|
||||
SYS(fail, "ip link add dst type veth peer name dst_fwd");
|
||||
|
||||
SYS(fail, "ip link set dst_fwd address " MAC_DST_FWD);
|
||||
SYS(fail, "ip link set dst address " MAC_DST);
|
||||
SYS(fail, "ip link add src address " MAC_SRC " type veth "
|
||||
"peer name src_fwd address " MAC_SRC_FWD);
|
||||
SYS(fail, "ip link add dst address " MAC_DST " type veth "
|
||||
"peer name dst_fwd address " MAC_DST_FWD);
|
||||
} else if (result->dev_mode == MODE_NETKIT) {
|
||||
err = create_netkit(NETKIT_L3, "src", "src_fwd");
|
||||
if (!ASSERT_OK(err, "create_ifindex_src"))
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2025 Meta Platforms Inc. */
|
||||
#include <test_progs.h>
|
||||
#include "test_btf_ext.skel.h"
|
||||
#include "btf_helpers.h"
|
||||
|
||||
static void subtest_line_func_info(void)
|
||||
{
|
||||
struct test_btf_ext *skel;
|
||||
struct bpf_prog_info info;
|
||||
struct bpf_line_info line_info[128], *libbpf_line_info;
|
||||
struct bpf_func_info func_info[128], *libbpf_func_info;
|
||||
__u32 info_len = sizeof(info), libbbpf_line_info_cnt, libbbpf_func_info_cnt;
|
||||
int err, fd;
|
||||
|
||||
skel = test_btf_ext__open_and_load();
|
||||
if (!ASSERT_OK_PTR(skel, "skel_open_and_load"))
|
||||
return;
|
||||
|
||||
fd = bpf_program__fd(skel->progs.global_func);
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.line_info = ptr_to_u64(&line_info);
|
||||
info.nr_line_info = sizeof(line_info);
|
||||
info.line_info_rec_size = sizeof(*line_info);
|
||||
err = bpf_prog_get_info_by_fd(fd, &info, &info_len);
|
||||
if (!ASSERT_OK(err, "prog_line_info"))
|
||||
goto out;
|
||||
|
||||
libbpf_line_info = bpf_program__line_info(skel->progs.global_func);
|
||||
libbbpf_line_info_cnt = bpf_program__line_info_cnt(skel->progs.global_func);
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.func_info = ptr_to_u64(&func_info);
|
||||
info.nr_func_info = sizeof(func_info);
|
||||
info.func_info_rec_size = sizeof(*func_info);
|
||||
err = bpf_prog_get_info_by_fd(fd, &info, &info_len);
|
||||
if (!ASSERT_OK(err, "prog_func_info"))
|
||||
goto out;
|
||||
|
||||
libbpf_func_info = bpf_program__func_info(skel->progs.global_func);
|
||||
libbbpf_func_info_cnt = bpf_program__func_info_cnt(skel->progs.global_func);
|
||||
|
||||
if (!ASSERT_OK_PTR(libbpf_line_info, "bpf_program__line_info"))
|
||||
goto out;
|
||||
if (!ASSERT_EQ(libbbpf_line_info_cnt, info.nr_line_info, "line_info_cnt"))
|
||||
goto out;
|
||||
if (!ASSERT_OK_PTR(libbpf_func_info, "bpf_program__func_info"))
|
||||
goto out;
|
||||
if (!ASSERT_EQ(libbbpf_func_info_cnt, info.nr_func_info, "func_info_cnt"))
|
||||
goto out;
|
||||
ASSERT_MEMEQ(libbpf_line_info, line_info, libbbpf_line_info_cnt * sizeof(*line_info),
|
||||
"line_info");
|
||||
ASSERT_MEMEQ(libbpf_func_info, func_info, libbbpf_func_info_cnt * sizeof(*func_info),
|
||||
"func_info");
|
||||
out:
|
||||
test_btf_ext__destroy(skel);
|
||||
}
|
||||
|
||||
void test_btf_ext(void)
|
||||
{
|
||||
if (test__start_subtest("line_func_info"))
|
||||
subtest_line_func_info();
|
||||
}
|
||||
@@ -63,6 +63,9 @@ static void test_set_global_vars_succeeds(void)
|
||||
" -G \"var_eb = EB2\" "\
|
||||
" -G \"var_ec = EC2\" "\
|
||||
" -G \"var_b = 1\" "\
|
||||
" -G \"struct1.struct2.u.var_u8 = 170\" "\
|
||||
" -G \"union1.struct3.var_u8_l = 0xaa\" "\
|
||||
" -G \"union1.struct3.var_u8_h = 0xaa\" "\
|
||||
"-vl2 > %s", fix->veristat, fix->tmpfile);
|
||||
|
||||
read(fix->fd, fix->output, fix->sz);
|
||||
@@ -78,6 +81,8 @@ static void test_set_global_vars_succeeds(void)
|
||||
__CHECK_STR("_w=12 ", "var_eb = EB2");
|
||||
__CHECK_STR("_w=13 ", "var_ec = EC2");
|
||||
__CHECK_STR("_w=1 ", "var_b = 1");
|
||||
__CHECK_STR("_w=170 ", "struct1.struct2.u.var_u8 = 170");
|
||||
__CHECK_STR("_w=0xaaaa ", "union1.var_u16 = 0xaaaa");
|
||||
|
||||
out:
|
||||
teardown_fixture(fix);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "verifier_bounds_deduction_non_const.skel.h"
|
||||
#include "verifier_bounds_mix_sign_unsign.skel.h"
|
||||
#include "verifier_bpf_get_stack.skel.h"
|
||||
#include "verifier_bpf_trap.skel.h"
|
||||
#include "verifier_bswap.skel.h"
|
||||
#include "verifier_btf_ctx_access.skel.h"
|
||||
#include "verifier_btf_unreliable_prog.skel.h"
|
||||
@@ -148,6 +149,7 @@ void test_verifier_bounds_deduction(void) { RUN(verifier_bounds_deduction);
|
||||
void test_verifier_bounds_deduction_non_const(void) { RUN(verifier_bounds_deduction_non_const); }
|
||||
void test_verifier_bounds_mix_sign_unsign(void) { RUN(verifier_bounds_mix_sign_unsign); }
|
||||
void test_verifier_bpf_get_stack(void) { RUN(verifier_bpf_get_stack); }
|
||||
void test_verifier_bpf_trap(void) { RUN(verifier_bpf_trap); }
|
||||
void test_verifier_bswap(void) { RUN(verifier_bswap); }
|
||||
void test_verifier_btf_ctx_access(void) { RUN(verifier_btf_ctx_access); }
|
||||
void test_verifier_btf_unreliable_prog(void) { RUN(verifier_btf_unreliable_prog); }
|
||||
|
||||
@@ -351,9 +351,10 @@ void test_xdp_metadata(void)
|
||||
struct xdp_metadata2 *bpf_obj2 = NULL;
|
||||
struct xdp_metadata *bpf_obj = NULL;
|
||||
struct bpf_program *new_prog, *prog;
|
||||
struct bpf_devmap_val devmap_e = {};
|
||||
struct bpf_map *prog_arr, *devmap;
|
||||
struct nstoken *tok = NULL;
|
||||
__u32 queue_id = QUEUE_ID;
|
||||
struct bpf_map *prog_arr;
|
||||
struct xsk tx_xsk = {};
|
||||
struct xsk rx_xsk = {};
|
||||
__u32 val, key = 0;
|
||||
@@ -409,6 +410,13 @@ void test_xdp_metadata(void)
|
||||
bpf_program__set_ifindex(prog, rx_ifindex);
|
||||
bpf_program__set_flags(prog, BPF_F_XDP_DEV_BOUND_ONLY);
|
||||
|
||||
/* Make sure we can load a dev-bound program that performs
|
||||
* XDP_REDIRECT into a devmap.
|
||||
*/
|
||||
new_prog = bpf_object__find_program_by_name(bpf_obj->obj, "redirect");
|
||||
bpf_program__set_ifindex(new_prog, rx_ifindex);
|
||||
bpf_program__set_flags(new_prog, BPF_F_XDP_DEV_BOUND_ONLY);
|
||||
|
||||
if (!ASSERT_OK(xdp_metadata__load(bpf_obj), "load skeleton"))
|
||||
goto out;
|
||||
|
||||
@@ -423,6 +431,18 @@ void test_xdp_metadata(void)
|
||||
"update prog_arr"))
|
||||
goto out;
|
||||
|
||||
/* Make sure we can't add dev-bound programs to devmaps. */
|
||||
devmap = bpf_object__find_map_by_name(bpf_obj->obj, "dev_map");
|
||||
if (!ASSERT_OK_PTR(devmap, "no dev_map found"))
|
||||
goto out;
|
||||
|
||||
devmap_e.bpf_prog.fd = val;
|
||||
if (!ASSERT_ERR(bpf_map__update_elem(devmap, &key, sizeof(key),
|
||||
&devmap_e, sizeof(devmap_e),
|
||||
BPF_ANY),
|
||||
"update dev_map"))
|
||||
goto out;
|
||||
|
||||
/* Attach BPF program to RX interface. */
|
||||
|
||||
ret = bpf_xdp_attach(rx_ifindex,
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include <linux/bpf.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_endian.h>
|
||||
|
||||
long process_byte = 0;
|
||||
int verdict_dir = 0;
|
||||
int dropped = 0;
|
||||
int pkt_size = 0;
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_SOCKMAP);
|
||||
__uint(max_entries, 20);
|
||||
__type(key, int);
|
||||
__type(value, int);
|
||||
} sock_map_rx SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_SOCKMAP);
|
||||
__uint(max_entries, 20);
|
||||
__type(key, int);
|
||||
__type(value, int);
|
||||
} sock_map_tx SEC(".maps");
|
||||
|
||||
SEC("sk_skb/stream_parser")
|
||||
int prog_skb_parser(struct __sk_buff *skb)
|
||||
{
|
||||
return pkt_size;
|
||||
}
|
||||
|
||||
SEC("sk_skb/stream_verdict")
|
||||
int prog_skb_verdict(struct __sk_buff *skb)
|
||||
{
|
||||
int one = 1;
|
||||
int ret = bpf_sk_redirect_map(skb, &sock_map_rx, one, verdict_dir);
|
||||
|
||||
if (ret == SK_DROP)
|
||||
dropped++;
|
||||
__sync_fetch_and_add(&process_byte, skb->len);
|
||||
return ret;
|
||||
}
|
||||
|
||||
SEC("sk_skb/stream_verdict")
|
||||
int prog_skb_pass(struct __sk_buff *skb)
|
||||
{
|
||||
__sync_fetch_and_add(&process_byte, skb->len);
|
||||
return SK_PASS;
|
||||
}
|
||||
|
||||
SEC("sk_msg")
|
||||
int prog_skmsg_verdict(struct sk_msg_md *msg)
|
||||
{
|
||||
int one = 1;
|
||||
|
||||
__sync_fetch_and_add(&process_byte, msg->size);
|
||||
return bpf_msg_redirect_map(msg, &sock_map_tx, one, verdict_dir);
|
||||
}
|
||||
|
||||
SEC("sk_msg")
|
||||
int prog_skmsg_pass(struct sk_msg_md *msg)
|
||||
{
|
||||
__sync_fetch_and_add(&process_byte, msg->size);
|
||||
return SK_PASS;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
+12
-3
@@ -32,6 +32,7 @@ extern unsigned long CONFIG_NR_CPUS __kconfig;
|
||||
struct __qspinlock {
|
||||
union {
|
||||
atomic_t val;
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
struct {
|
||||
u8 locked;
|
||||
u8 pending;
|
||||
@@ -40,6 +41,17 @@ struct __qspinlock {
|
||||
u16 locked_pending;
|
||||
u16 tail;
|
||||
};
|
||||
#else
|
||||
struct {
|
||||
u16 tail;
|
||||
u16 locked_pending;
|
||||
};
|
||||
struct {
|
||||
u8 reserved[2];
|
||||
u8 pending;
|
||||
u8 locked;
|
||||
};
|
||||
#endif
|
||||
};
|
||||
};
|
||||
|
||||
@@ -95,9 +107,6 @@ struct arena_qnode {
|
||||
#define _Q_LOCKED_VAL (1U << _Q_LOCKED_OFFSET)
|
||||
#define _Q_PENDING_VAL (1U << _Q_PENDING_OFFSET)
|
||||
|
||||
#define likely(x) __builtin_expect(!!(x), 1)
|
||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
|
||||
struct arena_qnode __arena qnodes[_Q_MAX_CPUS][_Q_MAX_NODES];
|
||||
|
||||
static inline u32 encode_tail(int cpu, int idx)
|
||||
@@ -225,8 +225,9 @@
|
||||
#define CAN_USE_BPF_ST
|
||||
#endif
|
||||
|
||||
#if __clang_major__ >= 18 && defined(ENABLE_ATOMICS_TESTS) && \
|
||||
(defined(__TARGET_ARCH_arm64) || defined(__TARGET_ARCH_x86))
|
||||
#if __clang_major__ >= 18 && defined(ENABLE_ATOMICS_TESTS) && \
|
||||
(defined(__TARGET_ARCH_arm64) || defined(__TARGET_ARCH_x86) || \
|
||||
(defined(__TARGET_ARCH_riscv) && __riscv_xlen == 64))
|
||||
#define CAN_USE_LOAD_ACQ_STORE_REL
|
||||
#endif
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2025 Google LLC */
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
|
||||
/* From uapi/linux/dma-buf.h */
|
||||
#define DMA_BUF_NAME_LEN 32
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(key_size, DMA_BUF_NAME_LEN);
|
||||
__type(value, bool);
|
||||
__uint(max_entries, 5);
|
||||
} testbuf_hash SEC(".maps");
|
||||
|
||||
/*
|
||||
* Fields output by this iterator are delimited by newlines. Convert any
|
||||
* newlines in user-provided printed strings to spaces.
|
||||
*/
|
||||
static void sanitize_string(char *src, size_t size)
|
||||
{
|
||||
for (char *c = src; (size_t)(c - src) < size && *c; ++c)
|
||||
if (*c == '\n')
|
||||
*c = ' ';
|
||||
}
|
||||
|
||||
SEC("iter/dmabuf")
|
||||
int dmabuf_collector(struct bpf_iter__dmabuf *ctx)
|
||||
{
|
||||
const struct dma_buf *dmabuf = ctx->dmabuf;
|
||||
struct seq_file *seq = ctx->meta->seq;
|
||||
unsigned long inode = 0;
|
||||
size_t size;
|
||||
const char *pname, *exporter;
|
||||
char name[DMA_BUF_NAME_LEN] = {'\0'};
|
||||
|
||||
if (!dmabuf)
|
||||
return 0;
|
||||
|
||||
if (BPF_CORE_READ_INTO(&inode, dmabuf, file, f_inode, i_ino) ||
|
||||
bpf_core_read(&size, sizeof(size), &dmabuf->size) ||
|
||||
bpf_core_read(&pname, sizeof(pname), &dmabuf->name) ||
|
||||
bpf_core_read(&exporter, sizeof(exporter), &dmabuf->exp_name))
|
||||
return 1;
|
||||
|
||||
/* Buffers are not required to be named */
|
||||
if (pname) {
|
||||
if (bpf_probe_read_kernel(name, sizeof(name), pname))
|
||||
return 1;
|
||||
|
||||
/* Name strings can be provided by userspace */
|
||||
sanitize_string(name, sizeof(name));
|
||||
}
|
||||
|
||||
BPF_SEQ_PRINTF(seq, "%lu\n%llu\n%s\n%s\n", inode, size, name, exporter);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("syscall")
|
||||
int iter_dmabuf_for_each(const void *ctx)
|
||||
{
|
||||
struct dma_buf *d;
|
||||
|
||||
bpf_for_each(dmabuf, d) {
|
||||
char name[DMA_BUF_NAME_LEN];
|
||||
const char *pname;
|
||||
bool *found;
|
||||
long len;
|
||||
int i;
|
||||
|
||||
if (bpf_core_read(&pname, sizeof(pname), &d->name))
|
||||
return 1;
|
||||
|
||||
/* Buffers are not required to be named */
|
||||
if (!pname)
|
||||
continue;
|
||||
|
||||
len = bpf_probe_read_kernel_str(name, sizeof(name), pname);
|
||||
if (len < 0)
|
||||
return 1;
|
||||
|
||||
/*
|
||||
* The entire name buffer is used as a map key.
|
||||
* Zeroize any uninitialized trailing bytes after the NUL.
|
||||
*/
|
||||
bpf_for(i, len, DMA_BUF_NAME_LEN)
|
||||
name[i] = 0;
|
||||
|
||||
found = bpf_map_lookup_elem(&testbuf_hash, name);
|
||||
if (found) {
|
||||
bool t = true;
|
||||
|
||||
bpf_map_update_elem(&testbuf_hash, name, &t, BPF_EXIST);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -680,3 +680,233 @@ out:
|
||||
bpf_ringbuf_discard_dynptr(&ptr_buf, 0);
|
||||
return XDP_DROP;
|
||||
}
|
||||
|
||||
void *user_ptr;
|
||||
/* Contains the copy of the data pointed by user_ptr.
|
||||
* Size 384 to make it not fit into a single kernel chunk when copying
|
||||
* but less than the maximum bpf stack size (512).
|
||||
*/
|
||||
char expected_str[384];
|
||||
__u32 test_len[7] = {0/* placeholder */, 0, 1, 2, 255, 256, 257};
|
||||
|
||||
typedef int (*bpf_read_dynptr_fn_t)(struct bpf_dynptr *dptr, u32 off,
|
||||
u32 size, const void *unsafe_ptr);
|
||||
|
||||
/* Returns the offset just before the end of the maximum sized xdp fragment.
|
||||
* Any write larger than 32 bytes will be split between 2 fragments.
|
||||
*/
|
||||
__u32 xdp_near_frag_end_offset(void)
|
||||
{
|
||||
const __u32 headroom = 256;
|
||||
const __u32 max_frag_size = __PAGE_SIZE - headroom - sizeof(struct skb_shared_info);
|
||||
|
||||
/* 32 bytes before the approximate end of the fragment */
|
||||
return max_frag_size - 32;
|
||||
}
|
||||
|
||||
/* Use __always_inline on test_dynptr_probe[_str][_xdp]() and callbacks
|
||||
* of type bpf_read_dynptr_fn_t to prevent compiler from generating
|
||||
* indirect calls that make program fail to load with "unknown opcode" error.
|
||||
*/
|
||||
static __always_inline void test_dynptr_probe(void *ptr, bpf_read_dynptr_fn_t bpf_read_dynptr_fn)
|
||||
{
|
||||
char buf[sizeof(expected_str)];
|
||||
struct bpf_dynptr ptr_buf;
|
||||
int i;
|
||||
|
||||
if (bpf_get_current_pid_tgid() >> 32 != pid)
|
||||
return;
|
||||
|
||||
err = bpf_ringbuf_reserve_dynptr(&ringbuf, sizeof(buf), 0, &ptr_buf);
|
||||
|
||||
bpf_for(i, 0, ARRAY_SIZE(test_len)) {
|
||||
__u32 len = test_len[i];
|
||||
|
||||
err = err ?: bpf_read_dynptr_fn(&ptr_buf, 0, test_len[i], ptr);
|
||||
if (len > sizeof(buf))
|
||||
break;
|
||||
err = err ?: bpf_dynptr_read(&buf, len, &ptr_buf, 0, 0);
|
||||
|
||||
if (err || bpf_memcmp(expected_str, buf, len))
|
||||
err = 1;
|
||||
|
||||
/* Reset buffer and dynptr */
|
||||
__builtin_memset(buf, 0, sizeof(buf));
|
||||
err = err ?: bpf_dynptr_write(&ptr_buf, 0, buf, len, 0);
|
||||
}
|
||||
bpf_ringbuf_discard_dynptr(&ptr_buf, 0);
|
||||
}
|
||||
|
||||
static __always_inline void test_dynptr_probe_str(void *ptr,
|
||||
bpf_read_dynptr_fn_t bpf_read_dynptr_fn)
|
||||
{
|
||||
char buf[sizeof(expected_str)];
|
||||
struct bpf_dynptr ptr_buf;
|
||||
__u32 cnt, i;
|
||||
|
||||
if (bpf_get_current_pid_tgid() >> 32 != pid)
|
||||
return;
|
||||
|
||||
bpf_ringbuf_reserve_dynptr(&ringbuf, sizeof(buf), 0, &ptr_buf);
|
||||
|
||||
bpf_for(i, 0, ARRAY_SIZE(test_len)) {
|
||||
__u32 len = test_len[i];
|
||||
|
||||
cnt = bpf_read_dynptr_fn(&ptr_buf, 0, len, ptr);
|
||||
if (cnt != len)
|
||||
err = 1;
|
||||
|
||||
if (len > sizeof(buf))
|
||||
continue;
|
||||
err = err ?: bpf_dynptr_read(&buf, len, &ptr_buf, 0, 0);
|
||||
if (!len)
|
||||
continue;
|
||||
if (err || bpf_memcmp(expected_str, buf, len - 1) || buf[len - 1] != '\0')
|
||||
err = 1;
|
||||
}
|
||||
bpf_ringbuf_discard_dynptr(&ptr_buf, 0);
|
||||
}
|
||||
|
||||
static __always_inline void test_dynptr_probe_xdp(struct xdp_md *xdp, void *ptr,
|
||||
bpf_read_dynptr_fn_t bpf_read_dynptr_fn)
|
||||
{
|
||||
struct bpf_dynptr ptr_xdp;
|
||||
char buf[sizeof(expected_str)];
|
||||
__u32 off, i;
|
||||
|
||||
if (bpf_get_current_pid_tgid() >> 32 != pid)
|
||||
return;
|
||||
|
||||
off = xdp_near_frag_end_offset();
|
||||
err = bpf_dynptr_from_xdp(xdp, 0, &ptr_xdp);
|
||||
|
||||
bpf_for(i, 0, ARRAY_SIZE(test_len)) {
|
||||
__u32 len = test_len[i];
|
||||
|
||||
err = err ?: bpf_read_dynptr_fn(&ptr_xdp, off, len, ptr);
|
||||
if (len > sizeof(buf))
|
||||
continue;
|
||||
err = err ?: bpf_dynptr_read(&buf, len, &ptr_xdp, off, 0);
|
||||
if (err || bpf_memcmp(expected_str, buf, len))
|
||||
err = 1;
|
||||
/* Reset buffer and dynptr */
|
||||
__builtin_memset(buf, 0, sizeof(buf));
|
||||
err = err ?: bpf_dynptr_write(&ptr_xdp, off, buf, len, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static __always_inline void test_dynptr_probe_str_xdp(struct xdp_md *xdp, void *ptr,
|
||||
bpf_read_dynptr_fn_t bpf_read_dynptr_fn)
|
||||
{
|
||||
struct bpf_dynptr ptr_xdp;
|
||||
char buf[sizeof(expected_str)];
|
||||
__u32 cnt, off, i;
|
||||
|
||||
if (bpf_get_current_pid_tgid() >> 32 != pid)
|
||||
return;
|
||||
|
||||
off = xdp_near_frag_end_offset();
|
||||
err = bpf_dynptr_from_xdp(xdp, 0, &ptr_xdp);
|
||||
if (err)
|
||||
return;
|
||||
|
||||
bpf_for(i, 0, ARRAY_SIZE(test_len)) {
|
||||
__u32 len = test_len[i];
|
||||
|
||||
cnt = bpf_read_dynptr_fn(&ptr_xdp, off, len, ptr);
|
||||
if (cnt != len)
|
||||
err = 1;
|
||||
|
||||
if (len > sizeof(buf))
|
||||
continue;
|
||||
err = err ?: bpf_dynptr_read(&buf, len, &ptr_xdp, off, 0);
|
||||
|
||||
if (!len)
|
||||
continue;
|
||||
if (err || bpf_memcmp(expected_str, buf, len - 1) || buf[len - 1] != '\0')
|
||||
err = 1;
|
||||
|
||||
__builtin_memset(buf, 0, sizeof(buf));
|
||||
err = err ?: bpf_dynptr_write(&ptr_xdp, off, buf, len, 0);
|
||||
}
|
||||
}
|
||||
|
||||
SEC("xdp")
|
||||
int test_probe_read_user_dynptr(struct xdp_md *xdp)
|
||||
{
|
||||
test_dynptr_probe(user_ptr, bpf_probe_read_user_dynptr);
|
||||
if (!err)
|
||||
test_dynptr_probe_xdp(xdp, user_ptr, bpf_probe_read_user_dynptr);
|
||||
return XDP_PASS;
|
||||
}
|
||||
|
||||
SEC("xdp")
|
||||
int test_probe_read_kernel_dynptr(struct xdp_md *xdp)
|
||||
{
|
||||
test_dynptr_probe(expected_str, bpf_probe_read_kernel_dynptr);
|
||||
if (!err)
|
||||
test_dynptr_probe_xdp(xdp, expected_str, bpf_probe_read_kernel_dynptr);
|
||||
return XDP_PASS;
|
||||
}
|
||||
|
||||
SEC("xdp")
|
||||
int test_probe_read_user_str_dynptr(struct xdp_md *xdp)
|
||||
{
|
||||
test_dynptr_probe_str(user_ptr, bpf_probe_read_user_str_dynptr);
|
||||
if (!err)
|
||||
test_dynptr_probe_str_xdp(xdp, user_ptr, bpf_probe_read_user_str_dynptr);
|
||||
return XDP_PASS;
|
||||
}
|
||||
|
||||
SEC("xdp")
|
||||
int test_probe_read_kernel_str_dynptr(struct xdp_md *xdp)
|
||||
{
|
||||
test_dynptr_probe_str(expected_str, bpf_probe_read_kernel_str_dynptr);
|
||||
if (!err)
|
||||
test_dynptr_probe_str_xdp(xdp, expected_str, bpf_probe_read_kernel_str_dynptr);
|
||||
return XDP_PASS;
|
||||
}
|
||||
|
||||
SEC("fentry.s/" SYS_PREFIX "sys_nanosleep")
|
||||
int test_copy_from_user_dynptr(void *ctx)
|
||||
{
|
||||
test_dynptr_probe(user_ptr, bpf_copy_from_user_dynptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("fentry.s/" SYS_PREFIX "sys_nanosleep")
|
||||
int test_copy_from_user_str_dynptr(void *ctx)
|
||||
{
|
||||
test_dynptr_probe_str(user_ptr, bpf_copy_from_user_str_dynptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bpf_copy_data_from_user_task(struct bpf_dynptr *dptr, u32 off,
|
||||
u32 size, const void *unsafe_ptr)
|
||||
{
|
||||
struct task_struct *task = bpf_get_current_task_btf();
|
||||
|
||||
return bpf_copy_from_user_task_dynptr(dptr, off, size, unsafe_ptr, task);
|
||||
}
|
||||
|
||||
static int bpf_copy_data_from_user_task_str(struct bpf_dynptr *dptr, u32 off,
|
||||
u32 size, const void *unsafe_ptr)
|
||||
{
|
||||
struct task_struct *task = bpf_get_current_task_btf();
|
||||
|
||||
return bpf_copy_from_user_task_str_dynptr(dptr, off, size, unsafe_ptr, task);
|
||||
}
|
||||
|
||||
SEC("fentry.s/" SYS_PREFIX "sys_nanosleep")
|
||||
int test_copy_from_user_task_dynptr(void *ctx)
|
||||
{
|
||||
test_dynptr_probe(user_ptr, bpf_copy_data_from_user_task);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("fentry.s/" SYS_PREFIX "sys_nanosleep")
|
||||
int test_copy_from_user_task_str_dynptr(void *ctx)
|
||||
{
|
||||
test_dynptr_probe_str(user_ptr, bpf_copy_data_from_user_task_str);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (C) 2025. Huawei Technologies Co., Ltd */
|
||||
#include <linux/bpf.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
||||
struct inner_map_type {
|
||||
__uint(type, BPF_MAP_TYPE_ARRAY);
|
||||
__uint(key_size, 4);
|
||||
__uint(value_size, 4);
|
||||
__uint(max_entries, 1);
|
||||
} inner_map SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH_OF_MAPS);
|
||||
__uint(max_entries, 64);
|
||||
__type(key, int);
|
||||
__type(value, int);
|
||||
__array(values, struct inner_map_type);
|
||||
} outer_map SEC(".maps") = {
|
||||
.values = {
|
||||
[0] = &inner_map,
|
||||
},
|
||||
};
|
||||
@@ -7,8 +7,6 @@
|
||||
#include "bpf_misc.h"
|
||||
#include "bpf_compiler.h"
|
||||
|
||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
|
||||
static volatile int zero = 0;
|
||||
|
||||
int my_pid;
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
|
||||
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include "bpf_misc.h"
|
||||
#include "bpf_experimental.h"
|
||||
|
||||
struct node_data {
|
||||
struct bpf_list_node l;
|
||||
int key;
|
||||
};
|
||||
|
||||
#define private(name) SEC(".data." #name) __hidden __attribute__((aligned(8)))
|
||||
private(A) struct bpf_spin_lock glock;
|
||||
private(A) struct bpf_list_head ghead __contains(node_data, l);
|
||||
|
||||
#define list_entry(ptr, type, member) container_of(ptr, type, member)
|
||||
#define NR_NODES 16
|
||||
|
||||
int zero = 0;
|
||||
|
||||
SEC("syscall")
|
||||
__retval(0)
|
||||
long list_peek(void *ctx)
|
||||
{
|
||||
struct bpf_list_node *l_n;
|
||||
struct node_data *n;
|
||||
int i, err = 0;
|
||||
|
||||
bpf_spin_lock(&glock);
|
||||
l_n = bpf_list_front(&ghead);
|
||||
bpf_spin_unlock(&glock);
|
||||
if (l_n)
|
||||
return __LINE__;
|
||||
|
||||
bpf_spin_lock(&glock);
|
||||
l_n = bpf_list_back(&ghead);
|
||||
bpf_spin_unlock(&glock);
|
||||
if (l_n)
|
||||
return __LINE__;
|
||||
|
||||
for (i = zero; i < NR_NODES && can_loop; i++) {
|
||||
n = bpf_obj_new(typeof(*n));
|
||||
if (!n)
|
||||
return __LINE__;
|
||||
n->key = i;
|
||||
bpf_spin_lock(&glock);
|
||||
bpf_list_push_back(&ghead, &n->l);
|
||||
bpf_spin_unlock(&glock);
|
||||
}
|
||||
|
||||
bpf_spin_lock(&glock);
|
||||
|
||||
l_n = bpf_list_front(&ghead);
|
||||
if (!l_n) {
|
||||
err = __LINE__;
|
||||
goto done;
|
||||
}
|
||||
|
||||
n = list_entry(l_n, struct node_data, l);
|
||||
if (n->key != 0) {
|
||||
err = __LINE__;
|
||||
goto done;
|
||||
}
|
||||
|
||||
l_n = bpf_list_back(&ghead);
|
||||
if (!l_n) {
|
||||
err = __LINE__;
|
||||
goto done;
|
||||
}
|
||||
|
||||
n = list_entry(l_n, struct node_data, l);
|
||||
if (n->key != NR_NODES - 1) {
|
||||
err = __LINE__;
|
||||
goto done;
|
||||
}
|
||||
|
||||
done:
|
||||
bpf_spin_unlock(&glock);
|
||||
return err;
|
||||
}
|
||||
|
||||
#define TEST_FB(op, dolock) \
|
||||
SEC("syscall") \
|
||||
__failure __msg(MSG) \
|
||||
long test_##op##_spinlock_##dolock(void *ctx) \
|
||||
{ \
|
||||
struct bpf_list_node *l_n; \
|
||||
__u64 jiffies = 0; \
|
||||
\
|
||||
if (dolock) \
|
||||
bpf_spin_lock(&glock); \
|
||||
l_n = bpf_list_##op(&ghead); \
|
||||
if (l_n) \
|
||||
jiffies = bpf_jiffies64(); \
|
||||
if (dolock) \
|
||||
bpf_spin_unlock(&glock); \
|
||||
\
|
||||
return !!jiffies; \
|
||||
}
|
||||
|
||||
#define MSG "call bpf_list_{{(front|back).+}}; R0{{(_w)?}}=ptr_or_null_node_data(id={{[0-9]+}},non_own_ref"
|
||||
TEST_FB(front, true)
|
||||
TEST_FB(back, true)
|
||||
#undef MSG
|
||||
|
||||
#define MSG "bpf_spin_lock at off=0 must be held for bpf_list_head"
|
||||
TEST_FB(front, false)
|
||||
TEST_FB(back, false)
|
||||
#undef MSG
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
@@ -2,7 +2,6 @@
|
||||
/* Copyright (c) 2025 Meta */
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
//#include <bpf/bpf_tracing.h>
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
||||
|
||||
@@ -69,11 +69,11 @@ long rbtree_api_nolock_first(void *ctx)
|
||||
}
|
||||
|
||||
SEC("?tc")
|
||||
__failure __msg("rbtree_remove node input must be non-owning ref")
|
||||
__retval(0)
|
||||
long rbtree_api_remove_unadded_node(void *ctx)
|
||||
{
|
||||
struct node_data *n, *m;
|
||||
struct bpf_rb_node *res;
|
||||
struct bpf_rb_node *res_n, *res_m;
|
||||
|
||||
n = bpf_obj_new(typeof(*n));
|
||||
if (!n)
|
||||
@@ -88,19 +88,20 @@ long rbtree_api_remove_unadded_node(void *ctx)
|
||||
bpf_spin_lock(&glock);
|
||||
bpf_rbtree_add(&groot, &n->node, less);
|
||||
|
||||
/* This remove should pass verifier */
|
||||
res = bpf_rbtree_remove(&groot, &n->node);
|
||||
n = container_of(res, struct node_data, node);
|
||||
res_n = bpf_rbtree_remove(&groot, &n->node);
|
||||
|
||||
/* This remove shouldn't, m isn't in an rbtree */
|
||||
res = bpf_rbtree_remove(&groot, &m->node);
|
||||
m = container_of(res, struct node_data, node);
|
||||
res_m = bpf_rbtree_remove(&groot, &m->node);
|
||||
bpf_spin_unlock(&glock);
|
||||
|
||||
if (n)
|
||||
bpf_obj_drop(n);
|
||||
if (m)
|
||||
bpf_obj_drop(m);
|
||||
bpf_obj_drop(m);
|
||||
if (res_n)
|
||||
bpf_obj_drop(container_of(res_n, struct node_data, node));
|
||||
if (res_m) {
|
||||
bpf_obj_drop(container_of(res_m, struct node_data, node));
|
||||
/* m was not added to the rbtree */
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -178,7 +179,7 @@ err_out:
|
||||
}
|
||||
|
||||
SEC("?tc")
|
||||
__failure __msg("rbtree_remove node input must be non-owning ref")
|
||||
__failure __msg("bpf_rbtree_remove can only take non-owning or refcounted bpf_rb_node pointer")
|
||||
long rbtree_api_add_release_unlock_escape(void *ctx)
|
||||
{
|
||||
struct node_data *n;
|
||||
@@ -202,7 +203,7 @@ long rbtree_api_add_release_unlock_escape(void *ctx)
|
||||
}
|
||||
|
||||
SEC("?tc")
|
||||
__failure __msg("rbtree_remove node input must be non-owning ref")
|
||||
__failure __msg("bpf_rbtree_remove can only take non-owning or refcounted bpf_rb_node pointer")
|
||||
long rbtree_api_first_release_unlock_escape(void *ctx)
|
||||
{
|
||||
struct bpf_rb_node *res;
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
|
||||
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include "bpf_misc.h"
|
||||
#include "bpf_experimental.h"
|
||||
|
||||
struct node_data {
|
||||
struct bpf_refcount ref;
|
||||
struct bpf_rb_node r0;
|
||||
struct bpf_rb_node r1;
|
||||
int key0;
|
||||
int key1;
|
||||
};
|
||||
|
||||
#define private(name) SEC(".data." #name) __hidden __attribute__((aligned(8)))
|
||||
private(A) struct bpf_spin_lock glock0;
|
||||
private(A) struct bpf_rb_root groot0 __contains(node_data, r0);
|
||||
|
||||
private(B) struct bpf_spin_lock glock1;
|
||||
private(B) struct bpf_rb_root groot1 __contains(node_data, r1);
|
||||
|
||||
#define rb_entry(ptr, type, member) container_of(ptr, type, member)
|
||||
#define NR_NODES 16
|
||||
|
||||
int zero = 0;
|
||||
|
||||
static bool less0(struct bpf_rb_node *a, const struct bpf_rb_node *b)
|
||||
{
|
||||
struct node_data *node_a;
|
||||
struct node_data *node_b;
|
||||
|
||||
node_a = rb_entry(a, struct node_data, r0);
|
||||
node_b = rb_entry(b, struct node_data, r0);
|
||||
|
||||
return node_a->key0 < node_b->key0;
|
||||
}
|
||||
|
||||
static bool less1(struct bpf_rb_node *a, const struct bpf_rb_node *b)
|
||||
{
|
||||
struct node_data *node_a;
|
||||
struct node_data *node_b;
|
||||
|
||||
node_a = rb_entry(a, struct node_data, r1);
|
||||
node_b = rb_entry(b, struct node_data, r1);
|
||||
|
||||
return node_a->key1 < node_b->key1;
|
||||
}
|
||||
|
||||
SEC("syscall")
|
||||
__retval(0)
|
||||
long rbtree_search(void *ctx)
|
||||
{
|
||||
struct bpf_rb_node *rb_n, *rb_m, *gc_ns[NR_NODES];
|
||||
long lookup_key = NR_NODES / 2;
|
||||
struct node_data *n, *m;
|
||||
int i, nr_gc = 0;
|
||||
|
||||
for (i = zero; i < NR_NODES && can_loop; i++) {
|
||||
n = bpf_obj_new(typeof(*n));
|
||||
if (!n)
|
||||
return __LINE__;
|
||||
|
||||
m = bpf_refcount_acquire(n);
|
||||
|
||||
n->key0 = i;
|
||||
m->key1 = i;
|
||||
|
||||
bpf_spin_lock(&glock0);
|
||||
bpf_rbtree_add(&groot0, &n->r0, less0);
|
||||
bpf_spin_unlock(&glock0);
|
||||
|
||||
bpf_spin_lock(&glock1);
|
||||
bpf_rbtree_add(&groot1, &m->r1, less1);
|
||||
bpf_spin_unlock(&glock1);
|
||||
}
|
||||
|
||||
n = NULL;
|
||||
bpf_spin_lock(&glock0);
|
||||
rb_n = bpf_rbtree_root(&groot0);
|
||||
while (can_loop) {
|
||||
if (!rb_n) {
|
||||
bpf_spin_unlock(&glock0);
|
||||
return __LINE__;
|
||||
}
|
||||
|
||||
n = rb_entry(rb_n, struct node_data, r0);
|
||||
if (lookup_key == n->key0)
|
||||
break;
|
||||
if (nr_gc < NR_NODES)
|
||||
gc_ns[nr_gc++] = rb_n;
|
||||
if (lookup_key < n->key0)
|
||||
rb_n = bpf_rbtree_left(&groot0, rb_n);
|
||||
else
|
||||
rb_n = bpf_rbtree_right(&groot0, rb_n);
|
||||
}
|
||||
|
||||
if (!n || lookup_key != n->key0) {
|
||||
bpf_spin_unlock(&glock0);
|
||||
return __LINE__;
|
||||
}
|
||||
|
||||
for (i = 0; i < nr_gc; i++) {
|
||||
rb_n = gc_ns[i];
|
||||
gc_ns[i] = bpf_rbtree_remove(&groot0, rb_n);
|
||||
}
|
||||
|
||||
m = bpf_refcount_acquire(n);
|
||||
bpf_spin_unlock(&glock0);
|
||||
|
||||
for (i = 0; i < nr_gc; i++) {
|
||||
rb_n = gc_ns[i];
|
||||
if (rb_n) {
|
||||
n = rb_entry(rb_n, struct node_data, r0);
|
||||
bpf_obj_drop(n);
|
||||
}
|
||||
}
|
||||
|
||||
if (!m)
|
||||
return __LINE__;
|
||||
|
||||
bpf_spin_lock(&glock1);
|
||||
rb_m = bpf_rbtree_remove(&groot1, &m->r1);
|
||||
bpf_spin_unlock(&glock1);
|
||||
bpf_obj_drop(m);
|
||||
if (!rb_m)
|
||||
return __LINE__;
|
||||
bpf_obj_drop(rb_entry(rb_m, struct node_data, r1));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define TEST_ROOT(dolock) \
|
||||
SEC("syscall") \
|
||||
__failure __msg(MSG) \
|
||||
long test_root_spinlock_##dolock(void *ctx) \
|
||||
{ \
|
||||
struct bpf_rb_node *rb_n; \
|
||||
__u64 jiffies = 0; \
|
||||
\
|
||||
if (dolock) \
|
||||
bpf_spin_lock(&glock0); \
|
||||
rb_n = bpf_rbtree_root(&groot0); \
|
||||
if (rb_n) \
|
||||
jiffies = bpf_jiffies64(); \
|
||||
if (dolock) \
|
||||
bpf_spin_unlock(&glock0); \
|
||||
\
|
||||
return !!jiffies; \
|
||||
}
|
||||
|
||||
#define TEST_LR(op, dolock) \
|
||||
SEC("syscall") \
|
||||
__failure __msg(MSG) \
|
||||
long test_##op##_spinlock_##dolock(void *ctx) \
|
||||
{ \
|
||||
struct bpf_rb_node *rb_n; \
|
||||
struct node_data *n; \
|
||||
__u64 jiffies = 0; \
|
||||
\
|
||||
bpf_spin_lock(&glock0); \
|
||||
rb_n = bpf_rbtree_root(&groot0); \
|
||||
if (!rb_n) { \
|
||||
bpf_spin_unlock(&glock0); \
|
||||
return 1; \
|
||||
} \
|
||||
n = rb_entry(rb_n, struct node_data, r0); \
|
||||
n = bpf_refcount_acquire(n); \
|
||||
bpf_spin_unlock(&glock0); \
|
||||
if (!n) \
|
||||
return 1; \
|
||||
\
|
||||
if (dolock) \
|
||||
bpf_spin_lock(&glock0); \
|
||||
rb_n = bpf_rbtree_##op(&groot0, &n->r0); \
|
||||
if (rb_n) \
|
||||
jiffies = bpf_jiffies64(); \
|
||||
if (dolock) \
|
||||
bpf_spin_unlock(&glock0); \
|
||||
\
|
||||
return !!jiffies; \
|
||||
}
|
||||
|
||||
/*
|
||||
* Use a spearate MSG macro instead of passing to TEST_XXX(..., MSG)
|
||||
* to ensure the message itself is not in the bpf prog lineinfo
|
||||
* which the verifier includes in its log.
|
||||
* Otherwise, the test_loader will incorrectly match the prog lineinfo
|
||||
* instead of the log generated by the verifier.
|
||||
*/
|
||||
#define MSG "call bpf_rbtree_root{{.+}}; R0{{(_w)?}}=rcu_ptr_or_null_node_data(id={{[0-9]+}},non_own_ref"
|
||||
TEST_ROOT(true)
|
||||
#undef MSG
|
||||
#define MSG "call bpf_rbtree_{{(left|right).+}}; R0{{(_w)?}}=rcu_ptr_or_null_node_data(id={{[0-9]+}},non_own_ref"
|
||||
TEST_LR(left, true)
|
||||
TEST_LR(right, true)
|
||||
#undef MSG
|
||||
|
||||
#define MSG "bpf_spin_lock at off=0 must be held for bpf_rb_root"
|
||||
TEST_ROOT(false)
|
||||
TEST_LR(left, false)
|
||||
TEST_LR(right, false)
|
||||
#undef MSG
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
@@ -24,6 +24,44 @@ const volatile enum Enumu64 var_eb = EB1;
|
||||
const volatile enum Enums64 var_ec = EC1;
|
||||
const volatile bool var_b = false;
|
||||
|
||||
struct Struct {
|
||||
int:16;
|
||||
__u16 filler;
|
||||
struct {
|
||||
const __u16 filler2;
|
||||
};
|
||||
struct Struct2 {
|
||||
__u16 filler;
|
||||
volatile struct {
|
||||
const int:1;
|
||||
union {
|
||||
const volatile __u8 var_u8;
|
||||
const volatile __s16 filler3;
|
||||
const int:1;
|
||||
} u;
|
||||
};
|
||||
} struct2;
|
||||
};
|
||||
|
||||
const volatile __u32 stru = 0; /* same prefix as below */
|
||||
const volatile struct Struct struct1 = {.struct2 = {.u = {.var_u8 = 1}}};
|
||||
|
||||
union Union {
|
||||
__u16 var_u16;
|
||||
struct Struct3 {
|
||||
struct {
|
||||
__u8 var_u8_l;
|
||||
};
|
||||
struct {
|
||||
struct {
|
||||
__u8 var_u8_h;
|
||||
};
|
||||
};
|
||||
} struct3;
|
||||
};
|
||||
|
||||
const volatile union Union union1 = {.var_u16 = -1};
|
||||
|
||||
char arr[4] = {0};
|
||||
|
||||
SEC("socket")
|
||||
@@ -43,5 +81,8 @@ int test_set_globals(void *ctx)
|
||||
a = var_eb;
|
||||
a = var_ec;
|
||||
a = var_b;
|
||||
a = struct1.struct2.u.var_u8;
|
||||
a = union1.var_u16;
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2025 Meta Platforms Inc. */
|
||||
|
||||
#include <linux/bpf.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include "bpf_misc.h"
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
||||
__noinline static void f0(void)
|
||||
{
|
||||
__u64 a = 1;
|
||||
|
||||
__sink(a);
|
||||
}
|
||||
|
||||
SEC("xdp")
|
||||
__u64 global_func(struct xdp_md *xdp)
|
||||
{
|
||||
f0();
|
||||
return XDP_DROP;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include <linux/bpf.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_endian.h>
|
||||
|
||||
int cork_byte;
|
||||
int push_start;
|
||||
int push_end;
|
||||
int apply_bytes;
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_SOCKMAP);
|
||||
__uint(max_entries, 20);
|
||||
__type(key, int);
|
||||
__type(value, int);
|
||||
} sock_map SEC(".maps");
|
||||
|
||||
SEC("sk_msg")
|
||||
int prog_sk_policy(struct sk_msg_md *msg)
|
||||
{
|
||||
if (cork_byte > 0)
|
||||
bpf_msg_cork_bytes(msg, cork_byte);
|
||||
if (push_start > 0 && push_end > 0)
|
||||
bpf_msg_push_data(msg, push_start, push_end, 0);
|
||||
|
||||
return SK_PASS;
|
||||
}
|
||||
|
||||
SEC("sk_msg")
|
||||
int prog_sk_policy_redir(struct sk_msg_md *msg)
|
||||
{
|
||||
int two = 2;
|
||||
|
||||
bpf_msg_apply_bytes(msg, apply_bytes);
|
||||
return bpf_msg_redirect_map(msg, &sock_map, two, 0);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include <linux/bpf.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include "bpf_misc.h"
|
||||
|
||||
SEC(".maps") struct {
|
||||
__uint(type, BPF_MAP_TYPE_SOCKMAP);
|
||||
__uint(max_entries, 1);
|
||||
__type(key, __u32);
|
||||
__type(value, __u64);
|
||||
} nop_map, sock_map;
|
||||
|
||||
SEC(".maps") struct {
|
||||
__uint(type, BPF_MAP_TYPE_SOCKHASH);
|
||||
__uint(max_entries, 1);
|
||||
__type(key, __u32);
|
||||
__type(value, __u64);
|
||||
} nop_hash, sock_hash;
|
||||
|
||||
SEC(".maps") struct {
|
||||
__uint(type, BPF_MAP_TYPE_ARRAY);
|
||||
__uint(max_entries, 2);
|
||||
__type(key, int);
|
||||
__type(value, unsigned int);
|
||||
} verdict_map;
|
||||
|
||||
/* Set by user space */
|
||||
int redirect_type;
|
||||
int redirect_flags;
|
||||
|
||||
#define redirect_map(__data) \
|
||||
_Generic((__data), \
|
||||
struct __sk_buff * : bpf_sk_redirect_map, \
|
||||
struct sk_msg_md * : bpf_msg_redirect_map \
|
||||
)((__data), &sock_map, (__u32){0}, redirect_flags)
|
||||
|
||||
#define redirect_hash(__data) \
|
||||
_Generic((__data), \
|
||||
struct __sk_buff * : bpf_sk_redirect_hash, \
|
||||
struct sk_msg_md * : bpf_msg_redirect_hash \
|
||||
)((__data), &sock_hash, &(__u32){0}, redirect_flags)
|
||||
|
||||
#define DEFINE_PROG(__type, __param) \
|
||||
SEC("sk_" XSTR(__type)) \
|
||||
int prog_ ## __type ## _verdict(__param data) \
|
||||
{ \
|
||||
unsigned int *count; \
|
||||
int verdict; \
|
||||
\
|
||||
if (redirect_type == BPF_MAP_TYPE_SOCKMAP) \
|
||||
verdict = redirect_map(data); \
|
||||
else if (redirect_type == BPF_MAP_TYPE_SOCKHASH) \
|
||||
verdict = redirect_hash(data); \
|
||||
else \
|
||||
verdict = redirect_type - __MAX_BPF_MAP_TYPE; \
|
||||
\
|
||||
count = bpf_map_lookup_elem(&verdict_map, &verdict); \
|
||||
if (count) \
|
||||
(*count)++; \
|
||||
\
|
||||
return verdict; \
|
||||
}
|
||||
|
||||
DEFINE_PROG(skb, struct __sk_buff *);
|
||||
DEFINE_PROG(msg, struct sk_msg_md *);
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
@@ -294,7 +294,9 @@ static int tcp_validate_sysctl(struct tcp_syncookie *ctx)
|
||||
(ctx->ipv6 && ctx->attrs.mss != MSS_LOCAL_IPV6))
|
||||
goto err;
|
||||
|
||||
if (!ctx->attrs.wscale_ok || ctx->attrs.snd_wscale != 7)
|
||||
if (!ctx->attrs.wscale_ok ||
|
||||
!ctx->attrs.snd_wscale ||
|
||||
ctx->attrs.snd_wscale >= BPF_SYNCOOKIE_WSCALE_MASK)
|
||||
goto err;
|
||||
|
||||
if (!ctx->attrs.tstamp_ok)
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include "bpf_misc.h"
|
||||
|
||||
#if __clang_major__ >= 21 && 0
|
||||
SEC("socket")
|
||||
__description("__builtin_trap with simple c code")
|
||||
__failure __msg("unexpected __bpf_trap() due to uninitialized variable?")
|
||||
void bpf_builtin_trap_with_simple_c(void)
|
||||
{
|
||||
__builtin_trap();
|
||||
}
|
||||
#endif
|
||||
|
||||
SEC("socket")
|
||||
__description("__bpf_trap with simple c code")
|
||||
__failure __msg("unexpected __bpf_trap() due to uninitialized variable?")
|
||||
void bpf_trap_with_simple_c(void)
|
||||
{
|
||||
__bpf_trap();
|
||||
}
|
||||
|
||||
SEC("socket")
|
||||
__description("__bpf_trap as the second-from-last insn")
|
||||
__failure __msg("unexpected __bpf_trap() due to uninitialized variable?")
|
||||
__naked void bpf_trap_at_func_end(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r0 = 0;"
|
||||
"call %[__bpf_trap];"
|
||||
"exit;"
|
||||
:
|
||||
: __imm(__bpf_trap)
|
||||
: __clobber_all);
|
||||
}
|
||||
|
||||
SEC("socket")
|
||||
__description("dead code __bpf_trap in the middle of code")
|
||||
__success
|
||||
__naked void dead_bpf_trap_in_middle(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r0 = 0;"
|
||||
"if r0 == 0 goto +1;"
|
||||
"call %[__bpf_trap];"
|
||||
"r0 = 2;"
|
||||
"exit;"
|
||||
:
|
||||
: __imm(__bpf_trap)
|
||||
: __clobber_all);
|
||||
}
|
||||
|
||||
SEC("socket")
|
||||
__description("reachable __bpf_trap in the middle of code")
|
||||
__failure __msg("unexpected __bpf_trap() due to uninitialized variable?")
|
||||
__naked void live_bpf_trap_in_middle(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r0 = 0;"
|
||||
"if r0 == 1 goto +1;"
|
||||
"call %[__bpf_trap];"
|
||||
"r0 = 2;"
|
||||
"exit;"
|
||||
:
|
||||
: __imm(__bpf_trap)
|
||||
: __clobber_all);
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
@@ -65,4 +65,16 @@ __naked void ctx_access_u32_pointer_reject_8(void)
|
||||
" ::: __clobber_all);
|
||||
}
|
||||
|
||||
SEC("fentry/bpf_fentry_test10")
|
||||
__description("btf_ctx_access const void pointer accept")
|
||||
__success __retval(0)
|
||||
__naked void ctx_access_const_void_pointer_accept(void)
|
||||
{
|
||||
asm volatile (" \
|
||||
r2 = *(u64 *)(r1 + 0); /* load 1st argument value (const void pointer) */\
|
||||
r0 = 0; \
|
||||
exit; \
|
||||
" ::: __clobber_all);
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
||||
@@ -10,65 +10,81 @@
|
||||
|
||||
SEC("socket")
|
||||
__description("load-acquire, 8-bit")
|
||||
__success __success_unpriv __retval(0x12)
|
||||
__success __success_unpriv __retval(0)
|
||||
__naked void load_acquire_8(void)
|
||||
{
|
||||
asm volatile (
|
||||
"w1 = 0x12;"
|
||||
"r0 = 0;"
|
||||
"w1 = 0xfe;"
|
||||
"*(u8 *)(r10 - 1) = w1;"
|
||||
".8byte %[load_acquire_insn];" // w0 = load_acquire((u8 *)(r10 - 1));
|
||||
".8byte %[load_acquire_insn];" // w2 = load_acquire((u8 *)(r10 - 1));
|
||||
"if r2 == r1 goto 1f;"
|
||||
"r0 = 1;"
|
||||
"1:"
|
||||
"exit;"
|
||||
:
|
||||
: __imm_insn(load_acquire_insn,
|
||||
BPF_ATOMIC_OP(BPF_B, BPF_LOAD_ACQ, BPF_REG_0, BPF_REG_10, -1))
|
||||
BPF_ATOMIC_OP(BPF_B, BPF_LOAD_ACQ, BPF_REG_2, BPF_REG_10, -1))
|
||||
: __clobber_all);
|
||||
}
|
||||
|
||||
SEC("socket")
|
||||
__description("load-acquire, 16-bit")
|
||||
__success __success_unpriv __retval(0x1234)
|
||||
__success __success_unpriv __retval(0)
|
||||
__naked void load_acquire_16(void)
|
||||
{
|
||||
asm volatile (
|
||||
"w1 = 0x1234;"
|
||||
"r0 = 0;"
|
||||
"w1 = 0xfedc;"
|
||||
"*(u16 *)(r10 - 2) = w1;"
|
||||
".8byte %[load_acquire_insn];" // w0 = load_acquire((u16 *)(r10 - 2));
|
||||
".8byte %[load_acquire_insn];" // w2 = load_acquire((u16 *)(r10 - 2));
|
||||
"if r2 == r1 goto 1f;"
|
||||
"r0 = 1;"
|
||||
"1:"
|
||||
"exit;"
|
||||
:
|
||||
: __imm_insn(load_acquire_insn,
|
||||
BPF_ATOMIC_OP(BPF_H, BPF_LOAD_ACQ, BPF_REG_0, BPF_REG_10, -2))
|
||||
BPF_ATOMIC_OP(BPF_H, BPF_LOAD_ACQ, BPF_REG_2, BPF_REG_10, -2))
|
||||
: __clobber_all);
|
||||
}
|
||||
|
||||
SEC("socket")
|
||||
__description("load-acquire, 32-bit")
|
||||
__success __success_unpriv __retval(0x12345678)
|
||||
__success __success_unpriv __retval(0)
|
||||
__naked void load_acquire_32(void)
|
||||
{
|
||||
asm volatile (
|
||||
"w1 = 0x12345678;"
|
||||
"r0 = 0;"
|
||||
"w1 = 0xfedcba09;"
|
||||
"*(u32 *)(r10 - 4) = w1;"
|
||||
".8byte %[load_acquire_insn];" // w0 = load_acquire((u32 *)(r10 - 4));
|
||||
".8byte %[load_acquire_insn];" // w2 = load_acquire((u32 *)(r10 - 4));
|
||||
"if r2 == r1 goto 1f;"
|
||||
"r0 = 1;"
|
||||
"1:"
|
||||
"exit;"
|
||||
:
|
||||
: __imm_insn(load_acquire_insn,
|
||||
BPF_ATOMIC_OP(BPF_W, BPF_LOAD_ACQ, BPF_REG_0, BPF_REG_10, -4))
|
||||
BPF_ATOMIC_OP(BPF_W, BPF_LOAD_ACQ, BPF_REG_2, BPF_REG_10, -4))
|
||||
: __clobber_all);
|
||||
}
|
||||
|
||||
SEC("socket")
|
||||
__description("load-acquire, 64-bit")
|
||||
__success __success_unpriv __retval(0x1234567890abcdef)
|
||||
__success __success_unpriv __retval(0)
|
||||
__naked void load_acquire_64(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r1 = 0x1234567890abcdef ll;"
|
||||
"r0 = 0;"
|
||||
"r1 = 0xfedcba0987654321 ll;"
|
||||
"*(u64 *)(r10 - 8) = r1;"
|
||||
".8byte %[load_acquire_insn];" // r0 = load_acquire((u64 *)(r10 - 8));
|
||||
".8byte %[load_acquire_insn];" // r2 = load_acquire((u64 *)(r10 - 8));
|
||||
"if r2 == r1 goto 1f;"
|
||||
"r0 = 1;"
|
||||
"1:"
|
||||
"exit;"
|
||||
:
|
||||
: __imm_insn(load_acquire_insn,
|
||||
BPF_ATOMIC_OP(BPF_DW, BPF_LOAD_ACQ, BPF_REG_0, BPF_REG_10, -8))
|
||||
BPF_ATOMIC_OP(BPF_DW, BPF_LOAD_ACQ, BPF_REG_2, BPF_REG_10, -8))
|
||||
: __clobber_all);
|
||||
}
|
||||
|
||||
|
||||
@@ -91,8 +91,7 @@ __naked int bpf_end_bswap(void)
|
||||
::: __clobber_all);
|
||||
}
|
||||
|
||||
#if defined(ENABLE_ATOMICS_TESTS) && \
|
||||
(defined(__TARGET_ARCH_arm64) || defined(__TARGET_ARCH_x86))
|
||||
#ifdef CAN_USE_LOAD_ACQ_STORE_REL
|
||||
|
||||
SEC("?raw_tp")
|
||||
__success __log_level(2)
|
||||
@@ -138,7 +137,7 @@ __naked int bpf_store_release(void)
|
||||
: __clobber_all);
|
||||
}
|
||||
|
||||
#endif /* load-acquire, store-release */
|
||||
#endif /* CAN_USE_LOAD_ACQ_STORE_REL */
|
||||
#endif /* v4 instruction */
|
||||
|
||||
SEC("?raw_tp")
|
||||
@@ -179,4 +178,57 @@ __naked int state_loop_first_last_equal(void)
|
||||
);
|
||||
}
|
||||
|
||||
__used __naked static void __bpf_cond_op_r10(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r2 = 2314885393468386424 ll;"
|
||||
"goto +0;"
|
||||
"if r2 <= r10 goto +3;"
|
||||
"if r1 >= -1835016 goto +0;"
|
||||
"if r2 <= 8 goto +0;"
|
||||
"if r3 <= 0 goto +0;"
|
||||
"exit;"
|
||||
::: __clobber_all);
|
||||
}
|
||||
|
||||
SEC("?raw_tp")
|
||||
__success __log_level(2)
|
||||
__msg("8: (bd) if r2 <= r10 goto pc+3")
|
||||
__msg("9: (35) if r1 >= 0xffe3fff8 goto pc+0")
|
||||
__msg("10: (b5) if r2 <= 0x8 goto pc+0")
|
||||
__msg("mark_precise: frame1: last_idx 10 first_idx 0 subseq_idx -1")
|
||||
__msg("mark_precise: frame1: regs=r2 stack= before 9: (35) if r1 >= 0xffe3fff8 goto pc+0")
|
||||
__msg("mark_precise: frame1: regs=r2 stack= before 8: (bd) if r2 <= r10 goto pc+3")
|
||||
__msg("mark_precise: frame1: regs=r2 stack= before 7: (05) goto pc+0")
|
||||
__naked void bpf_cond_op_r10(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r3 = 0 ll;"
|
||||
"call __bpf_cond_op_r10;"
|
||||
"r0 = 0;"
|
||||
"exit;"
|
||||
::: __clobber_all);
|
||||
}
|
||||
|
||||
SEC("?raw_tp")
|
||||
__success __log_level(2)
|
||||
__msg("3: (bf) r3 = r10")
|
||||
__msg("4: (bd) if r3 <= r2 goto pc+1")
|
||||
__msg("5: (b5) if r2 <= 0x8 goto pc+2")
|
||||
__msg("mark_precise: frame0: last_idx 5 first_idx 0 subseq_idx -1")
|
||||
__msg("mark_precise: frame0: regs=r2 stack= before 4: (bd) if r3 <= r2 goto pc+1")
|
||||
__msg("mark_precise: frame0: regs=r2 stack= before 3: (bf) r3 = r10")
|
||||
__naked void bpf_cond_op_not_r10(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r0 = 0;"
|
||||
"r2 = 2314885393468386424 ll;"
|
||||
"r3 = r10;"
|
||||
"if r3 <= r2 goto +1;"
|
||||
"if r2 <= 8 goto +2;"
|
||||
"r0 = 2 ll;"
|
||||
"exit;"
|
||||
::: __clobber_all);
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
||||
@@ -6,18 +6,21 @@
|
||||
#include "../../../include/linux/filter.h"
|
||||
#include "bpf_misc.h"
|
||||
|
||||
#if __clang_major__ >= 18 && defined(ENABLE_ATOMICS_TESTS) && \
|
||||
(defined(__TARGET_ARCH_arm64) || defined(__TARGET_ARCH_x86))
|
||||
#ifdef CAN_USE_LOAD_ACQ_STORE_REL
|
||||
|
||||
SEC("socket")
|
||||
__description("store-release, 8-bit")
|
||||
__success __success_unpriv __retval(0x12)
|
||||
__success __success_unpriv __retval(0)
|
||||
__naked void store_release_8(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r0 = 0;"
|
||||
"w1 = 0x12;"
|
||||
".8byte %[store_release_insn];" // store_release((u8 *)(r10 - 1), w1);
|
||||
"w0 = *(u8 *)(r10 - 1);"
|
||||
"w2 = *(u8 *)(r10 - 1);"
|
||||
"if r2 == r1 goto 1f;"
|
||||
"r0 = 1;"
|
||||
"1:"
|
||||
"exit;"
|
||||
:
|
||||
: __imm_insn(store_release_insn,
|
||||
@@ -27,13 +30,17 @@ __naked void store_release_8(void)
|
||||
|
||||
SEC("socket")
|
||||
__description("store-release, 16-bit")
|
||||
__success __success_unpriv __retval(0x1234)
|
||||
__success __success_unpriv __retval(0)
|
||||
__naked void store_release_16(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r0 = 0;"
|
||||
"w1 = 0x1234;"
|
||||
".8byte %[store_release_insn];" // store_release((u16 *)(r10 - 2), w1);
|
||||
"w0 = *(u16 *)(r10 - 2);"
|
||||
"w2 = *(u16 *)(r10 - 2);"
|
||||
"if r2 == r1 goto 1f;"
|
||||
"r0 = 1;"
|
||||
"1:"
|
||||
"exit;"
|
||||
:
|
||||
: __imm_insn(store_release_insn,
|
||||
@@ -43,13 +50,17 @@ __naked void store_release_16(void)
|
||||
|
||||
SEC("socket")
|
||||
__description("store-release, 32-bit")
|
||||
__success __success_unpriv __retval(0x12345678)
|
||||
__success __success_unpriv __retval(0)
|
||||
__naked void store_release_32(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r0 = 0;"
|
||||
"w1 = 0x12345678;"
|
||||
".8byte %[store_release_insn];" // store_release((u32 *)(r10 - 4), w1);
|
||||
"w0 = *(u32 *)(r10 - 4);"
|
||||
"w2 = *(u32 *)(r10 - 4);"
|
||||
"if r2 == r1 goto 1f;"
|
||||
"r0 = 1;"
|
||||
"1:"
|
||||
"exit;"
|
||||
:
|
||||
: __imm_insn(store_release_insn,
|
||||
@@ -59,13 +70,17 @@ __naked void store_release_32(void)
|
||||
|
||||
SEC("socket")
|
||||
__description("store-release, 64-bit")
|
||||
__success __success_unpriv __retval(0x1234567890abcdef)
|
||||
__success __success_unpriv __retval(0)
|
||||
__naked void store_release_64(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r0 = 0;"
|
||||
"r1 = 0x1234567890abcdef ll;"
|
||||
".8byte %[store_release_insn];" // store_release((u64 *)(r10 - 8), r1);
|
||||
"r0 = *(u64 *)(r10 - 8);"
|
||||
"r2 = *(u64 *)(r10 - 8);"
|
||||
"if r2 == r1 goto 1f;"
|
||||
"r0 = 1;"
|
||||
"1:"
|
||||
"exit;"
|
||||
:
|
||||
: __imm_insn(store_release_insn,
|
||||
@@ -271,7 +286,7 @@ __naked void store_release_with_invalid_reg(void)
|
||||
: __clobber_all);
|
||||
}
|
||||
|
||||
#else
|
||||
#else /* CAN_USE_LOAD_ACQ_STORE_REL */
|
||||
|
||||
SEC("socket")
|
||||
__description("Clang version < 18, ENABLE_ATOMICS_TESTS not defined, and/or JIT doesn't support store-release, use a dummy test")
|
||||
@@ -281,6 +296,6 @@ int dummy_test(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif /* CAN_USE_LOAD_ACQ_STORE_REL */
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
||||
@@ -19,6 +19,13 @@ struct {
|
||||
__type(value, __u32);
|
||||
} prog_arr SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_DEVMAP);
|
||||
__uint(key_size, sizeof(__u32));
|
||||
__uint(value_size, sizeof(struct bpf_devmap_val));
|
||||
__uint(max_entries, 1);
|
||||
} dev_map SEC(".maps");
|
||||
|
||||
extern int bpf_xdp_metadata_rx_timestamp(const struct xdp_md *ctx,
|
||||
__u64 *timestamp) __ksym;
|
||||
extern int bpf_xdp_metadata_rx_hash(const struct xdp_md *ctx, __u32 *hash,
|
||||
@@ -95,4 +102,10 @@ int rx(struct xdp_md *ctx)
|
||||
return bpf_redirect_map(&xsk, ctx->rx_queue_index, XDP_PASS);
|
||||
}
|
||||
|
||||
SEC("xdp")
|
||||
int redirect(struct xdp_md *ctx)
|
||||
{
|
||||
return bpf_redirect_map(&dev_map, ctx->rx_queue_index, XDP_PASS);
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
||||
@@ -134,6 +134,10 @@ bpf_testmod_test_arg_ptr_to_struct(struct bpf_testmod_struct_arg_1 *a) {
|
||||
return bpf_testmod_test_struct_arg_result;
|
||||
}
|
||||
|
||||
__weak noinline void bpf_testmod_looooooooooooooooooooooooooooooong_name(void)
|
||||
{
|
||||
}
|
||||
|
||||
__bpf_kfunc void
|
||||
bpf_testmod_test_mod_kfunc(int i)
|
||||
{
|
||||
@@ -1340,7 +1344,7 @@ static int st_ops_gen_prologue_with_kfunc(struct bpf_insn *insn_buf, bool direct
|
||||
*insn++ = BPF_STX_MEM(BPF_DW, BPF_REG_6, BPF_REG_7, offsetof(struct st_ops_args, a));
|
||||
*insn++ = BPF_JMP_IMM(BPF_JA, 0, 0, 2);
|
||||
*insn++ = BPF_MOV64_REG(BPF_REG_1, BPF_REG_0);
|
||||
*insn++ = BPF_CALL_KFUNC(0, bpf_cgroup_release_id),
|
||||
*insn++ = BPF_CALL_KFUNC(0, bpf_cgroup_release_id);
|
||||
*insn++ = BPF_MOV64_REG(BPF_REG_1, BPF_REG_8);
|
||||
*insn++ = prog->insnsi[0];
|
||||
|
||||
@@ -1379,7 +1383,7 @@ static int st_ops_gen_epilogue_with_kfunc(struct bpf_insn *insn_buf, const struc
|
||||
*insn++ = BPF_STX_MEM(BPF_DW, BPF_REG_1, BPF_REG_6, offsetof(struct st_ops_args, a));
|
||||
*insn++ = BPF_JMP_IMM(BPF_JA, 0, 0, 2);
|
||||
*insn++ = BPF_MOV64_REG(BPF_REG_1, BPF_REG_0);
|
||||
*insn++ = BPF_CALL_KFUNC(0, bpf_cgroup_release_id),
|
||||
*insn++ = BPF_CALL_KFUNC(0, bpf_cgroup_release_id);
|
||||
*insn++ = BPF_MOV64_REG(BPF_REG_0, BPF_REG_6);
|
||||
*insn++ = BPF_ALU64_IMM(BPF_MUL, BPF_REG_0, 2);
|
||||
*insn++ = BPF_EXIT_INSN();
|
||||
|
||||
@@ -1042,6 +1042,14 @@ void run_subtest(struct test_loader *tester,
|
||||
emit_verifier_log(tester->log_buf, false /*force*/);
|
||||
validate_msgs(tester->log_buf, &subspec->expect_msgs, emit_verifier_log);
|
||||
|
||||
/* Restore capabilities because the kernel will silently ignore requests
|
||||
* for program info (such as xlated program text) if we are not
|
||||
* bpf-capable. Also, for some reason test_verifier executes programs
|
||||
* with all capabilities restored. Do the same here.
|
||||
*/
|
||||
if (restore_capabilities(&caps))
|
||||
goto tobj_cleanup;
|
||||
|
||||
if (subspec->expect_xlated.cnt) {
|
||||
err = get_xlated_program_text(bpf_program__fd(tprog),
|
||||
tester->log_buf, tester->log_buf_sz);
|
||||
@@ -1067,12 +1075,6 @@ void run_subtest(struct test_loader *tester,
|
||||
}
|
||||
|
||||
if (should_do_test_run(spec, subspec)) {
|
||||
/* For some reason test_verifier executes programs
|
||||
* with all capabilities restored. Do the same here.
|
||||
*/
|
||||
if (restore_capabilities(&caps))
|
||||
goto tobj_cleanup;
|
||||
|
||||
/* Do bpf_map__attach_struct_ops() for each struct_ops map.
|
||||
* This should trigger bpf_struct_ops->reg callback on kernel side.
|
||||
*/
|
||||
|
||||
@@ -734,7 +734,7 @@ static __u32 btf_raw_types[] = {
|
||||
BTF_MEMBER_ENC(71, 13, 128), /* struct prog_test_member __kptr *ptr; */
|
||||
};
|
||||
|
||||
static char bpf_vlog[UINT_MAX >> 8];
|
||||
static char bpf_vlog[UINT_MAX >> 5];
|
||||
|
||||
static int load_btf_spec(__u32 *types, int types_len,
|
||||
const char *strings, int strings_len)
|
||||
@@ -1559,10 +1559,10 @@ static void do_test_single(struct bpf_test *test, bool unpriv,
|
||||
test->errstr_unpriv : test->errstr;
|
||||
|
||||
opts.expected_attach_type = test->expected_attach_type;
|
||||
if (verbose)
|
||||
opts.log_level = verif_log_level | 4; /* force stats */
|
||||
else if (expected_ret == VERBOSE_ACCEPT)
|
||||
if (expected_ret == VERBOSE_ACCEPT)
|
||||
opts.log_level = 2;
|
||||
else if (verbose)
|
||||
opts.log_level = verif_log_level | 4; /* force stats */
|
||||
else
|
||||
opts.log_level = DEFAULT_LIBBPF_LOG_LEVEL;
|
||||
opts.prog_flags = pflags;
|
||||
|
||||
@@ -1486,7 +1486,84 @@ static bool is_preset_supported(const struct btf_type *t)
|
||||
return btf_is_int(t) || btf_is_enum(t) || btf_is_enum64(t);
|
||||
}
|
||||
|
||||
static int set_global_var(struct bpf_object *obj, struct btf *btf, const struct btf_type *t,
|
||||
const int btf_find_member(const struct btf *btf,
|
||||
const struct btf_type *parent_type,
|
||||
__u32 parent_offset,
|
||||
const char *member_name,
|
||||
int *member_tid,
|
||||
__u32 *member_offset)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!btf_is_composite(parent_type))
|
||||
return -EINVAL;
|
||||
|
||||
for (i = 0; i < btf_vlen(parent_type); ++i) {
|
||||
const struct btf_member *member;
|
||||
const struct btf_type *member_type;
|
||||
int tid;
|
||||
|
||||
member = btf_members(parent_type) + i;
|
||||
tid = btf__resolve_type(btf, member->type);
|
||||
if (tid < 0)
|
||||
return -EINVAL;
|
||||
|
||||
member_type = btf__type_by_id(btf, tid);
|
||||
if (member->name_off) {
|
||||
const char *name = btf__name_by_offset(btf, member->name_off);
|
||||
|
||||
if (strcmp(member_name, name) == 0) {
|
||||
if (btf_member_bitfield_size(parent_type, i) != 0) {
|
||||
fprintf(stderr, "Bitfield presets are not supported %s\n",
|
||||
name);
|
||||
return -EINVAL;
|
||||
}
|
||||
*member_offset = parent_offset + member->offset;
|
||||
*member_tid = tid;
|
||||
return 0;
|
||||
}
|
||||
} else if (btf_is_composite(member_type)) {
|
||||
int err;
|
||||
|
||||
err = btf_find_member(btf, member_type, parent_offset + member->offset,
|
||||
member_name, member_tid, member_offset);
|
||||
if (!err)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int adjust_var_secinfo(struct btf *btf, const struct btf_type *t,
|
||||
struct btf_var_secinfo *sinfo, const char *var)
|
||||
{
|
||||
char expr[256], *saveptr;
|
||||
const struct btf_type *base_type, *member_type;
|
||||
int err, member_tid;
|
||||
char *name;
|
||||
__u32 member_offset = 0;
|
||||
|
||||
base_type = btf__type_by_id(btf, btf__resolve_type(btf, t->type));
|
||||
snprintf(expr, sizeof(expr), "%s", var);
|
||||
strtok_r(expr, ".", &saveptr);
|
||||
|
||||
while ((name = strtok_r(NULL, ".", &saveptr))) {
|
||||
err = btf_find_member(btf, base_type, 0, name, &member_tid, &member_offset);
|
||||
if (err) {
|
||||
fprintf(stderr, "Could not find member %s for variable %s\n", name, var);
|
||||
return err;
|
||||
}
|
||||
member_type = btf__type_by_id(btf, member_tid);
|
||||
sinfo->offset += member_offset / 8;
|
||||
sinfo->size = member_type->size;
|
||||
sinfo->type = member_tid;
|
||||
base_type = member_type;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int set_global_var(struct bpf_object *obj, struct btf *btf,
|
||||
struct bpf_map *map, struct btf_var_secinfo *sinfo,
|
||||
struct var_preset *preset)
|
||||
{
|
||||
@@ -1495,9 +1572,9 @@ static int set_global_var(struct bpf_object *obj, struct btf *btf, const struct
|
||||
long long value = preset->ivalue;
|
||||
size_t size;
|
||||
|
||||
base_type = btf__type_by_id(btf, btf__resolve_type(btf, t->type));
|
||||
base_type = btf__type_by_id(btf, btf__resolve_type(btf, sinfo->type));
|
||||
if (!base_type) {
|
||||
fprintf(stderr, "Failed to resolve type %d\n", t->type);
|
||||
fprintf(stderr, "Failed to resolve type %d\n", sinfo->type);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!is_preset_supported(base_type)) {
|
||||
@@ -1530,7 +1607,7 @@ static int set_global_var(struct bpf_object *obj, struct btf *btf, const struct
|
||||
if (value >= max_val || value < -max_val) {
|
||||
fprintf(stderr,
|
||||
"Variable %s value %lld is out of range [%lld; %lld]\n",
|
||||
btf__name_by_offset(btf, t->name_off), value,
|
||||
btf__name_by_offset(btf, base_type->name_off), value,
|
||||
is_signed ? -max_val : 0, max_val - 1);
|
||||
return -EINVAL;
|
||||
}
|
||||
@@ -1583,14 +1660,20 @@ static int set_global_vars(struct bpf_object *obj, struct var_preset *presets, i
|
||||
for (j = 0; j < n; ++j, ++sinfo) {
|
||||
const struct btf_type *var_type = btf__type_by_id(btf, sinfo->type);
|
||||
const char *var_name;
|
||||
int var_len;
|
||||
|
||||
if (!btf_is_var(var_type))
|
||||
continue;
|
||||
|
||||
var_name = btf__name_by_offset(btf, var_type->name_off);
|
||||
var_len = strlen(var_name);
|
||||
|
||||
for (k = 0; k < npresets; ++k) {
|
||||
if (strcmp(var_name, presets[k].name) != 0)
|
||||
struct btf_var_secinfo tmp_sinfo;
|
||||
|
||||
if (strncmp(var_name, presets[k].name, var_len) != 0 ||
|
||||
(presets[k].name[var_len] != '\0' &&
|
||||
presets[k].name[var_len] != '.'))
|
||||
continue;
|
||||
|
||||
if (presets[k].applied) {
|
||||
@@ -1598,13 +1681,17 @@ static int set_global_vars(struct bpf_object *obj, struct var_preset *presets, i
|
||||
var_name);
|
||||
return -EINVAL;
|
||||
}
|
||||
tmp_sinfo = *sinfo;
|
||||
err = adjust_var_secinfo(btf, var_type,
|
||||
&tmp_sinfo, presets[k].name);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = set_global_var(obj, btf, var_type, map, sinfo, presets + k);
|
||||
err = set_global_var(obj, btf, map, &tmp_sinfo, presets + k);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
presets[k].applied = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user