diff options
Diffstat (limited to 'lib/libxdp/tests/test_xsk_refcnt.c')
-rw-r--r-- | lib/libxdp/tests/test_xsk_refcnt.c | 304 |
1 files changed, 304 insertions, 0 deletions
diff --git a/lib/libxdp/tests/test_xsk_refcnt.c b/lib/libxdp/tests/test_xsk_refcnt.c new file mode 100644 index 0000000..bdd22da --- /dev/null +++ b/lib/libxdp/tests/test_xsk_refcnt.c @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) + +#include <errno.h> +#include <linux/err.h> +#include <net/if.h> +#include <pthread.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <sys/resource.h> +#include <unistd.h> + +#include "test_utils.h" + +#include <xdp/libxdp.h> +#include <xdp/xsk.h> + +typedef __u64 u64; +typedef __u32 u32; +typedef __u16 u16; +typedef __u8 u8; + +#define MAX_EVENTS 10 +#define MAX_NUM_QUEUES 4 +#define TEST_NAME_LENGTH 128 + +struct xsk_umem_info { + struct xsk_ring_prod fq; + struct xsk_ring_cons cq; + struct xsk_umem *umem; + void *buffer; +}; + +struct xsk_socket_info { + struct xsk_ring_cons rx; + struct xsk_umem_info *umem; + struct xsk_socket *xsk; +}; + +/* Event holds socket operations that are run concurrently + * and in theory can produce a race condition + */ +struct xsk_test_event { + u32 num_create; + u32 num_delete; + u32 create_qids[MAX_NUM_QUEUES]; /* QIDs for sockets being created in this event */ + u32 delete_qids[MAX_NUM_QUEUES]; /* QIDs for sockets being deleted in this event */ +}; + +struct xsk_test { + char name[TEST_NAME_LENGTH]; + u32 num_events; + struct xsk_test_event events[MAX_EVENTS]; +}; + +/* Tests that use less queues must come first, + * so we can run all possible tests on VMs with + * small number of CPUs + */ +static struct xsk_test all_tests[] = { + { "Single socket created and deleted", + .num_events = 2, + .events = {{ .num_create = 1, .create_qids = {0} }, + { .num_delete = 1, .delete_qids = {0} } + }}, + { "2 sockets, created and deleted sequentially", + .num_events = 4, + .events = {{ .num_create = 1, .create_qids = {0} }, + { .num_create = 1, .create_qids = {1} }, + { .num_delete = 1, .delete_qids = {0} }, + { .num_delete = 1, .delete_qids = {1} } + }}, + { "2 sockets, created sequentially and deleted asynchronously", + .num_events = 3, + .events = {{ .num_create = 1, .create_qids = {0} }, + { .num_create = 1, .create_qids = {1} }, + { .num_delete = 2, .delete_qids = {0, 1} } + }}, + { "2 sockets, asynchronously delete and create", + .num_events = 3, + .events = {{ .num_create = 1, .create_qids = {0} }, + { .num_create = 1, .create_qids = {1}, + .num_delete = 1, .delete_qids = {0} }, + { .num_delete = 1, .delete_qids = {1} } + }}, + { "3 sockets, created and deleted sequentially", + .num_events = 6, + .events = {{ .num_create = 1, .create_qids = {0} }, + { .num_create = 1, .create_qids = {1} }, + { .num_create = 1, .create_qids = {2} }, + { .num_delete = 1, .delete_qids = {1} }, + { .num_delete = 1, .delete_qids = {2} }, + { .num_delete = 1, .delete_qids = {0} } + }}, +}; + +# define ARRAY_SIZE(_x) (sizeof(_x) / sizeof((_x)[0])) + +static const char *opt_if; +static const u8 num_tests = ARRAY_SIZE(all_tests); + +static struct xsk_socket_info *xsks[MAX_NUM_QUEUES]; + +#define FRAME_SIZE 64 +#define NUM_FRAMES (XSK_RING_CONS__DEFAULT_NUM_DESCS * 2) + +static struct xsk_umem_info *xsk_configure_umem(void *buffer, u64 size) +{ + struct xsk_umem_info *umem; + int ret; + + umem = calloc(1, sizeof(*umem)); + if (!umem) + exit(EXIT_FAILURE); + + ret = xsk_umem__create(&umem->umem, buffer, size, &umem->fq, &umem->cq, + NULL); + if (ret) + exit(ret); + + umem->buffer = buffer; + return umem; +} + +static struct xsk_socket_info *xsk_configure_socket(struct xsk_umem_info *umem, + unsigned int qid) +{ + struct xsk_socket_config cfg = {}; + struct xsk_socket_info *xsk; + struct xsk_ring_cons *rxr; + + xsk = calloc(1, sizeof(*xsk)); + if (!xsk) + exit(EXIT_FAILURE); + + xsk->umem = umem; + cfg.rx_size = XSK_RING_CONS__DEFAULT_NUM_DESCS; + + rxr = &xsk->rx; + xsk_socket__create(&xsk->xsk, opt_if, qid, umem->umem, + rxr, NULL, &cfg); + + return xsk; +} + +static void *create_socket(void *args) +{ + struct xsk_umem_info *umem; + u32 qid = *(u32 *)args; + void *buffs; + + if (posix_memalign(&buffs, + getpagesize(), /* PAGE_SIZE aligned */ + NUM_FRAMES * FRAME_SIZE)) { + fprintf(stderr, "ERROR: Can't allocate buffer memory \"%s\"\n", + strerror(errno)); + exit(EXIT_FAILURE); + } + + umem = xsk_configure_umem(buffs, NUM_FRAMES * FRAME_SIZE); + xsks[qid] = xsk_configure_socket(umem, qid); + + return NULL; +} + +static void *delete_socket(void *args) +{ + u32 qid = *(u32 *)args; + struct xsk_umem *umem; + void *buff; + + buff = xsks[qid]->umem->buffer; + umem = xsks[qid]->umem->umem; + xsk_socket__delete(xsks[qid]->xsk); + free(buff); + (void)xsk_umem__delete(umem); + + return NULL; +} + +static bool xsk_prog_attached(void) +{ + char xsk_prog_name[] = "xsk_def_prog"; + int ifindex = if_nametoindex(opt_if); + struct xdp_program *xsk_prog; + struct xdp_multiprog *mp; + bool answer = false; + + mp = xdp_multiprog__get_from_ifindex(ifindex); + if (IS_ERR_OR_NULL(mp)) + return false; + + xsk_prog = xdp_multiprog__is_legacy(mp) ? xdp_multiprog__main_prog(mp) : + xdp_multiprog__next_prog(NULL, mp); + + if (IS_ERR_OR_NULL(xsk_prog)) + goto free_mp; + + answer = !strncmp(xsk_prog_name, xdp_program__name(xsk_prog), + sizeof(xsk_prog_name)); +free_mp: + xdp_multiprog__close(mp); + return answer; +} + +static void update_reference_refcnt(struct xsk_test_event *event, int *refcnt) +{ + *refcnt += event->num_create; + *refcnt -= event->num_delete; +} + +static bool check_run_event(struct xsk_test_event *event, int *refcnt) +{ + pthread_t threads[MAX_NUM_QUEUES]; + bool prog_attached, prog_needed; + u8 thread_num = 0, i; + int ret; + + update_reference_refcnt(event, refcnt); + + for (i = 0; i < event->num_create; i++) { + ret = pthread_create(&threads[thread_num++], NULL, + &create_socket, &event->create_qids[i]); + if (ret) + exit(ret); + } + + for (i = 0; i < event->num_delete; i++) { + ret = pthread_create(&threads[thread_num++], NULL, + &delete_socket, &event->delete_qids[i]); + if (ret) + exit(ret); + } + + for (i = 0; i < thread_num; i++) + pthread_join(threads[i], NULL); + + prog_attached = xsk_prog_attached(); + prog_needed = *refcnt > 0; + + if (prog_needed != prog_attached) { + printf("Program is referenced by %d sockets, but is %s attached\n", + *refcnt, prog_attached ? "still" : "not"); + return false; + } + + return true; +} + +static bool check_run_test(struct xsk_test *test) +{ + bool test_ok = false; + int refcnt = 0; + u8 i = 0; + + for (i = 0; i < test->num_events; i++) { + if (!check_run_event(&test->events[i], &refcnt)) { + printf("Event %u failed\n", i); + goto print_result; + } + } + + /* Do not let tests interfere with each other */ + sleep(1); + + test_ok = true; + +print_result: + printf("%s: %s\n", test->name, test_ok ? "PASSED" : "FAILED"); + return test_ok; +} + +static int read_args(int argc, char **argv) +{ + if (argc != 2) + return -1; + + opt_if = argv[1]; + return 0; +} + +int main(int argc, char **argv) +{ + struct rlimit r = {RLIM_INFINITY, RLIM_INFINITY}; + u8 i = 0; + + if (read_args(argc, argv)) + return -1; + + if (setrlimit(RLIMIT_MEMLOCK, &r)) { + fprintf(stderr, "ERROR: setrlimit(RLIMIT_MEMLOCK) \"%s\"\n", + strerror(errno)); + exit(EXIT_FAILURE); + } + + silence_libbpf_logging(); + + for (i = 0; i < num_tests; i++) { + if (!check_run_test(&all_tests[i])) + exit(EXIT_FAILURE); + } + + return 0; +} |