// SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2019 Facebook */ #include <linux/compiler.h> #include <linux/err.h> #include <sys/resource.h> #include <sys/socket.h> #include <sys/types.h> #include <linux/btf.h> #include <unistd.h> #include <signal.h> #include <errno.h> #include <string.h> #include <pthread.h> #include <bpf/bpf.h> #include <bpf/libbpf.h> #include <test_btf.h> #include <test_maps.h> static struct bpf_map_create_opts map_opts = { .sz = sizeof(map_opts), .btf_key_type_id = 1, .btf_value_type_id = 3, .btf_fd = -1, .map_flags = BPF_F_NO_PREALLOC, }; static unsigned int nr_sk_threads_done; static unsigned int nr_sk_threads_err; static unsigned int nr_sk_per_thread = 4096; static unsigned int nr_sk_threads = 4; static int sk_storage_map = -1; static unsigned int stop; static int runtime_s = 5; static bool is_stopped(void) { return READ_ONCE(stop); } static unsigned int threads_err(void) { return READ_ONCE(nr_sk_threads_err); } static void notify_thread_err(void) { __sync_add_and_fetch(&nr_sk_threads_err, 1); } static bool wait_for_threads_err(void) { while (!is_stopped() && !threads_err()) usleep(500); return !is_stopped(); } static unsigned int threads_done(void) { return READ_ONCE(nr_sk_threads_done); } static void notify_thread_done(void) { __sync_add_and_fetch(&nr_sk_threads_done, 1); } static void notify_thread_redo(void) { __sync_sub_and_fetch(&nr_sk_threads_done, 1); } static bool wait_for_threads_done(void) { while (threads_done() != nr_sk_threads && !is_stopped() && !threads_err()) usleep(50); return !is_stopped() && !threads_err(); } static bool wait_for_threads_redo(void) { while (threads_done() && !is_stopped() && !threads_err()) usleep(50); return !is_stopped() && !threads_err(); } static bool wait_for_map(void) { while (READ_ONCE(sk_storage_map) == -1 && !is_stopped()) usleep(50); return !is_stopped(); } static bool wait_for_map_close(void) { while (READ_ONCE(sk_storage_map) != -1 && !is_stopped()) ; return !is_stopped(); } static int load_btf(void) { const char btf_str_sec[] = "\0bpf_spin_lock\0val\0cnt\0l"; __u32 btf_raw_types[] = { /* int */ BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */ /* struct bpf_spin_lock */ /* [2] */ BTF_TYPE_ENC(1, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 1), 4), BTF_MEMBER_ENC(15, 1, 0), /* int val; */ /* struct val */ /* [3] */ BTF_TYPE_ENC(15, BTF_INFO_ENC(BTF_KIND_STRUCT, 0, 2), 8), BTF_MEMBER_ENC(19, 1, 0), /* int cnt; */ BTF_MEMBER_ENC(23, 2, 32),/* struct bpf_spin_lock l; */ }; struct btf_header btf_hdr = { .magic = BTF_MAGIC, .version = BTF_VERSION, .hdr_len = sizeof(struct btf_header), .type_len = sizeof(btf_raw_types), .str_off = sizeof(btf_raw_types), .str_len = sizeof(btf_str_sec), }; __u8 raw_btf[sizeof(struct btf_header) + sizeof(btf_raw_types) + sizeof(btf_str_sec)]; memcpy(raw_btf, &btf_hdr, sizeof(btf_hdr)); memcpy(raw_btf + sizeof(btf_hdr), btf_raw_types, sizeof(btf_raw_types)); memcpy(raw_btf + sizeof(btf_hdr) + sizeof(btf_raw_types), btf_str_sec, sizeof(btf_str_sec)); return bpf_btf_load(raw_btf, sizeof(raw_btf), NULL); } static int create_sk_storage_map(void) { int btf_fd, map_fd; btf_fd = load_btf(); CHECK(btf_fd == -1, "bpf_load_btf", "btf_fd:%d errno:%d\n", btf_fd, errno); map_opts.btf_fd = btf_fd; map_fd = bpf_map_create(BPF_MAP_TYPE_SK_STORAGE, "sk_storage_map", 4, 8, 0, &map_opts); map_opts.btf_fd = -1; close(btf_fd); CHECK(map_fd == -1, "bpf_map_create()", "errno:%d\n", errno); return map_fd; } static void *insert_close_thread(void *arg) { struct { int cnt; int lock; } value = { .cnt = 0xeB9F, .lock = 0, }; int i, map_fd, err, *sk_fds; sk_fds = malloc(sizeof(*sk_fds) * nr_sk_per_thread); if (!sk_fds) { notify_thread_err(); return ERR_PTR(-ENOMEM); } for (i = 0; i < nr_sk_per_thread; i++) sk_fds[i] = -1; while (!is_stopped()) { if (!wait_for_map()) goto close_all; map_fd = READ_ONCE(sk_storage_map); for (i = 0; i < nr_sk_per_thread && !is_stopped(); i++) { sk_fds[i] = socket(AF_INET6, SOCK_STREAM, 0); if (sk_fds[i] == -1) { err = -errno; fprintf(stderr, "socket(): errno:%d\n", errno); goto errout; } err = bpf_map_update_elem(map_fd, &sk_fds[i], &value, BPF_NOEXIST); if (err) { err = -errno; fprintf(stderr, "bpf_map_update_elem(): errno:%d\n", errno); goto errout; } } notify_thread_done(); wait_for_map_close(); close_all: for (i = 0; i < nr_sk_per_thread; i++) { close(sk_fds[i]); sk_fds[i] = -1; } notify_thread_redo(); } free(sk_fds); return NULL; errout: for (i = 0; i < nr_sk_per_thread && sk_fds[i] != -1; i++) close(sk_fds[i]); free(sk_fds); notify_thread_err(); return ERR_PTR(err); } static int do_sk_storage_map_stress_free(void) { int i, map_fd = -1, err = 0, nr_threads_created = 0; pthread_t *sk_thread_ids; void *thread_ret; sk_thread_ids = malloc(sizeof(pthread_t) * nr_sk_threads); if (!sk_thread_ids) { fprintf(stderr, "malloc(sk_threads): NULL\n"); return -ENOMEM; } for (i = 0; i < nr_sk_threads; i++) { err = pthread_create(&sk_thread_ids[i], NULL, insert_close_thread, NULL); if (err) { err = -errno; goto done; } nr_threads_created++; } while (!is_stopped()) { map_fd = create_sk_storage_map(); WRITE_ONCE(sk_storage_map, map_fd); if (!wait_for_threads_done()) break; WRITE_ONCE(sk_storage_map, -1); close(map_fd); map_fd = -1; if (!wait_for_threads_redo()) break; } done: WRITE_ONCE(stop, 1); for (i = 0; i < nr_threads_created; i++) { pthread_join(sk_thread_ids[i], &thread_ret); if (IS_ERR(thread_ret) && !err) { err = PTR_ERR(thread_ret); fprintf(stderr, "threads#%u: err:%d\n", i, err); } } free(sk_thread_ids); if (map_fd != -1) close(map_fd); return err; } static void *update_thread(void *arg) { struct { int cnt; int lock; } value = { .cnt = 0xeB9F, .lock = 0, }; int map_fd = READ_ONCE(sk_storage_map); int sk_fd = *(int *)arg; int err = 0; /* Suppress compiler false alarm */ while (!is_stopped()) { err = bpf_map_update_elem(map_fd, &sk_fd, &value, 0); if (err && errno != EAGAIN) { err = -errno; fprintf(stderr, "bpf_map_update_elem: %d %d\n", err, errno); break; } } if (!is_stopped()) { notify_thread_err(); return ERR_PTR(err); } return NULL; } static void *delete_thread(void *arg) { int map_fd = READ_ONCE(sk_storage_map); int sk_fd = *(int *)arg; int err = 0; /* Suppress compiler false alarm */ while (!is_stopped()) { err = bpf_map_delete_elem(map_fd, &sk_fd); if (err && errno != ENOENT) { err = -errno; fprintf(stderr, "bpf_map_delete_elem: %d %d\n", err, errno); break; } } if (!is_stopped()) { notify_thread_err(); return ERR_PTR(err); } return NULL; } static int do_sk_storage_map_stress_change(void) { int i, sk_fd, map_fd = -1, err = 0, nr_threads_created = 0; pthread_t *sk_thread_ids; void *thread_ret; sk_thread_ids = malloc(sizeof(pthread_t) * nr_sk_threads); if (!sk_thread_ids) { fprintf(stderr, "malloc(sk_threads): NULL\n"); return -ENOMEM; } sk_fd = socket(AF_INET6, SOCK_STREAM, 0); if (sk_fd == -1) { err = -errno; goto done; } map_fd = create_sk_storage_map(); WRITE_ONCE(sk_storage_map, map_fd); for (i = 0; i < nr_sk_threads; i++) { if (i & 0x1) err = pthread_create(&sk_thread_ids[i], NULL, update_thread, &sk_fd); else err = pthread_create(&sk_thread_ids[i], NULL, delete_thread, &sk_fd); if (err) { err = -errno; goto done; } nr_threads_created++; } wait_for_threads_err(); done: WRITE_ONCE(stop, 1); for (i = 0; i < nr_threads_created; i++) { pthread_join(sk_thread_ids[i], &thread_ret); if (IS_ERR(thread_ret) && !err) { err = PTR_ERR(thread_ret); fprintf(stderr, "threads#%u: err:%d\n", i, err); } } free(sk_thread_ids); if (sk_fd != -1) close(sk_fd); close(map_fd); return err; } static void stop_handler(int signum) { if (signum != SIGALRM) printf("stopping...\n"); WRITE_ONCE(stop, 1); } #define BPF_SK_STORAGE_MAP_TEST_NR_THREADS "BPF_SK_STORAGE_MAP_TEST_NR_THREADS" #define BPF_SK_STORAGE_MAP_TEST_SK_PER_THREAD "BPF_SK_STORAGE_MAP_TEST_SK_PER_THREAD" #define BPF_SK_STORAGE_MAP_TEST_RUNTIME_S "BPF_SK_STORAGE_MAP_TEST_RUNTIME_S" #define BPF_SK_STORAGE_MAP_TEST_NAME "BPF_SK_STORAGE_MAP_TEST_NAME" static void test_sk_storage_map_stress_free(void) { struct rlimit rlim_old, rlim_new = {}; int err; getrlimit(RLIMIT_NOFILE, &rlim_old); signal(SIGTERM, stop_handler); signal(SIGINT, stop_handler); if (runtime_s > 0) { signal(SIGALRM, stop_handler); alarm(runtime_s); } if (rlim_old.rlim_cur < nr_sk_threads * nr_sk_per_thread) { rlim_new.rlim_cur = nr_sk_threads * nr_sk_per_thread + 128; rlim_new.rlim_max = rlim_new.rlim_cur + 128; err = setrlimit(RLIMIT_NOFILE, &rlim_new); CHECK(err, "setrlimit(RLIMIT_NOFILE)", "rlim_new:%lu errno:%d", rlim_new.rlim_cur, errno); } err = do_sk_storage_map_stress_free(); signal(SIGTERM, SIG_DFL); signal(SIGINT, SIG_DFL); if (runtime_s > 0) { signal(SIGALRM, SIG_DFL); alarm(0); } if (rlim_new.rlim_cur) setrlimit(RLIMIT_NOFILE, &rlim_old); CHECK(err, "test_sk_storage_map_stress_free", "err:%d\n", err); } static void test_sk_storage_map_stress_change(void) { int err; signal(SIGTERM, stop_handler); signal(SIGINT, stop_handler); if (runtime_s > 0) { signal(SIGALRM, stop_handler); alarm(runtime_s); } err = do_sk_storage_map_stress_change(); signal(SIGTERM, SIG_DFL); signal(SIGINT, SIG_DFL); if (runtime_s > 0) { signal(SIGALRM, SIG_DFL); alarm(0); } CHECK(err, "test_sk_storage_map_stress_change", "err:%d\n", err); } static void test_sk_storage_map_basic(void) { struct { int cnt; int lock; } value = { .cnt = 0xeB9f, .lock = 1, }, lookup_value; struct bpf_map_create_opts bad_xattr; int btf_fd, map_fd, sk_fd, err; btf_fd = load_btf(); CHECK(btf_fd == -1, "bpf_load_btf", "btf_fd:%d errno:%d\n", btf_fd, errno); map_opts.btf_fd = btf_fd; sk_fd = socket(AF_INET6, SOCK_STREAM, 0); CHECK(sk_fd == -1, "socket()", "sk_fd:%d errno:%d\n", sk_fd, errno); map_fd = bpf_map_create(BPF_MAP_TYPE_SK_STORAGE, "sk_storage_map", 4, 8, 0, &map_opts); CHECK(map_fd == -1, "bpf_map_create(good_xattr)", "map_fd:%d errno:%d\n", map_fd, errno); /* Add new elem */ memcpy(&lookup_value, &value, sizeof(value)); err = bpf_map_update_elem(map_fd, &sk_fd, &value, BPF_NOEXIST | BPF_F_LOCK); CHECK(err, "bpf_map_update_elem(BPF_NOEXIST|BPF_F_LOCK)", "err:%d errno:%d\n", err, errno); err = bpf_map_lookup_elem_flags(map_fd, &sk_fd, &lookup_value, BPF_F_LOCK); CHECK(err || lookup_value.lock || lookup_value.cnt != value.cnt, "bpf_map_lookup_elem_flags(BPF_F_LOCK)", "err:%d errno:%d lock:%x cnt:%x(%x)\n", err, errno, lookup_value.lock, lookup_value.cnt, value.cnt); /* Bump the cnt and update with BPF_EXIST | BPF_F_LOCK */ value.cnt += 1; value.lock = 2; err = bpf_map_update_elem(map_fd, &sk_fd, &value, BPF_EXIST | BPF_F_LOCK); CHECK(err, "bpf_map_update_elem(BPF_EXIST|BPF_F_LOCK)", "err:%d errno:%d\n", err, errno); err = bpf_map_lookup_elem_flags(map_fd, &sk_fd, &lookup_value, BPF_F_LOCK); CHECK(err || lookup_value.lock || lookup_value.cnt != value.cnt, "bpf_map_lookup_elem_flags(BPF_F_LOCK)", "err:%d errno:%d lock:%x cnt:%x(%x)\n", err, errno, lookup_value.lock, lookup_value.cnt, value.cnt); /* Bump the cnt and update with BPF_EXIST */ value.cnt += 1; value.lock = 2; err = bpf_map_update_elem(map_fd, &sk_fd, &value, BPF_EXIST); CHECK(err, "bpf_map_update_elem(BPF_EXIST)", "err:%d errno:%d\n", err, errno); err = bpf_map_lookup_elem_flags(map_fd, &sk_fd, &lookup_value, BPF_F_LOCK); CHECK(err || lookup_value.lock || lookup_value.cnt != value.cnt, "bpf_map_lookup_elem_flags(BPF_F_LOCK)", "err:%d errno:%d lock:%x cnt:%x(%x)\n", err, errno, lookup_value.lock, lookup_value.cnt, value.cnt); /* Update with BPF_NOEXIST */ value.cnt += 1; value.lock = 2; err = bpf_map_update_elem(map_fd, &sk_fd, &value, BPF_NOEXIST | BPF_F_LOCK); CHECK(!err || errno != EEXIST, "bpf_map_update_elem(BPF_NOEXIST|BPF_F_LOCK)", "err:%d errno:%d\n", err, errno); err = bpf_map_update_elem(map_fd, &sk_fd, &value, BPF_NOEXIST); CHECK(!err || errno != EEXIST, "bpf_map_update_elem(BPF_NOEXIST)", "err:%d errno:%d\n", err, errno); value.cnt -= 1; err = bpf_map_lookup_elem_flags(map_fd, &sk_fd, &lookup_value, BPF_F_LOCK); CHECK(err || lookup_value.lock || lookup_value.cnt != value.cnt, "bpf_map_lookup_elem_flags(BPF_F_LOCK)", "err:%d errno:%d lock:%x cnt:%x(%x)\n", err, errno, lookup_value.lock, lookup_value.cnt, value.cnt); /* Bump the cnt again and update with map_flags == 0 */ value.cnt += 1; value.lock = 2; err = bpf_map_update_elem(map_fd, &sk_fd, &value, 0); CHECK(err, "bpf_map_update_elem()", "err:%d errno:%d\n", err, errno); err = bpf_map_lookup_elem_flags(map_fd, &sk_fd, &lookup_value, BPF_F_LOCK); CHECK(err || lookup_value.lock || lookup_value.cnt != value.cnt, "bpf_map_lookup_elem_flags(BPF_F_LOCK)", "err:%d errno:%d lock:%x cnt:%x(%x)\n", err, errno, lookup_value.lock, lookup_value.cnt, value.cnt); /* Test delete elem */ err = bpf_map_delete_elem(map_fd, &sk_fd); CHECK(err, "bpf_map_delete_elem()", "err:%d errno:%d\n", err, errno); err = bpf_map_lookup_elem_flags(map_fd, &sk_fd, &lookup_value, BPF_F_LOCK); CHECK(!err || errno != ENOENT, "bpf_map_lookup_elem_flags(BPF_F_LOCK)", "err:%d errno:%d\n", err, errno); err = bpf_map_delete_elem(map_fd, &sk_fd); CHECK(!err || errno != ENOENT, "bpf_map_delete_elem()", "err:%d errno:%d\n", err, errno); memcpy(&bad_xattr, &map_opts, sizeof(map_opts)); bad_xattr.btf_key_type_id = 0; err = bpf_map_create(BPF_MAP_TYPE_SK_STORAGE, "sk_storage_map", 4, 8, 0, &bad_xattr); CHECK(!err || errno != EINVAL, "bpf_map_create(bad_xattr)", "err:%d errno:%d\n", err, errno); memcpy(&bad_xattr, &map_opts, sizeof(map_opts)); bad_xattr.btf_key_type_id = 3; err = bpf_map_create(BPF_MAP_TYPE_SK_STORAGE, "sk_storage_map", 4, 8, 0, &bad_xattr); CHECK(!err || errno != EINVAL, "bpf_map_create(bad_xattr)", "err:%d errno:%d\n", err, errno); err = bpf_map_create(BPF_MAP_TYPE_SK_STORAGE, "sk_storage_map", 4, 8, 1, &map_opts); CHECK(!err || errno != EINVAL, "bpf_map_create(bad_xattr)", "err:%d errno:%d\n", err, errno); memcpy(&bad_xattr, &map_opts, sizeof(map_opts)); bad_xattr.map_flags = 0; err = bpf_map_create(BPF_MAP_TYPE_SK_STORAGE, "sk_storage_map", 4, 8, 0, &bad_xattr); CHECK(!err || errno != EINVAL, "bap_create_map_xattr(bad_xattr)", "err:%d errno:%d\n", err, errno); map_opts.btf_fd = -1; close(btf_fd); close(map_fd); close(sk_fd); } void test_sk_storage_map(void) { const char *test_name, *env_opt; bool test_ran = false; test_name = getenv(BPF_SK_STORAGE_MAP_TEST_NAME); env_opt = getenv(BPF_SK_STORAGE_MAP_TEST_NR_THREADS); if (env_opt) nr_sk_threads = atoi(env_opt); env_opt = getenv(BPF_SK_STORAGE_MAP_TEST_SK_PER_THREAD); if (env_opt) nr_sk_per_thread = atoi(env_opt); env_opt = getenv(BPF_SK_STORAGE_MAP_TEST_RUNTIME_S); if (env_opt) runtime_s = atoi(env_opt); if (!test_name || !strcmp(test_name, "basic")) { test_sk_storage_map_basic(); test_ran = true; } if (!test_name || !strcmp(test_name, "stress_free")) { test_sk_storage_map_stress_free(); test_ran = true; } if (!test_name || !strcmp(test_name, "stress_change")) { test_sk_storage_map_stress_change(); test_ran = true; } if (test_ran) printf("%s:PASS\n", __func__); else CHECK(1, "Invalid test_name", "%s\n", test_name); }