Shared-UMEM lifetime can be torn down from multiple setup and cleanup paths. Mixing manual UMEM free/unmap in one path with helper-based teardown in others makes ownership easier to violate and can leak or double-release UMEM during error handling. Track UMEM ownership with a refcount_t users field. Initialize it to 1 in xsk_configure_umem() and increment it for each additional shared socket. Introduce xsk_delete_socket() as the single teardown helper: it deletes the socket handle, clears state to prevent double-decrement, and releases the UMEM when the last reference drops. Replace testapp_clean_xsk_umem(), clean_sockets(), and clean_umem() with xsk_delete_socket_batch() and xsk_delete_all_ifobj_sockets() wrappers at all call sites. Convert ifobj_zc_avail() in xskxceiver.c to use stack allocation and xsk_delete_socket() for uniform cleanup. Signed-off-by: Magnus Karlsson Signed-off-by: Tushar Vyavahare --- .../selftests/bpf/prog_tests/test_xsk.c | 124 ++++++++++++------ .../selftests/bpf/prog_tests/test_xsk.h | 3 + tools/testing/selftests/bpf/xskxceiver.c | 35 ++--- 3 files changed, 103 insertions(+), 59 deletions(-) diff --git a/tools/testing/selftests/bpf/prog_tests/test_xsk.c b/tools/testing/selftests/bpf/prog_tests/test_xsk.c index 477aedbb01ba..4ccdb0825130 100644 --- a/tools/testing/selftests/bpf/prog_tests/test_xsk.c +++ b/tools/testing/selftests/bpf/prog_tests/test_xsk.c @@ -101,6 +101,7 @@ int xsk_configure_umem(struct ifobject *ifobj, struct xsk_umem_info *umem, void return ret; umem->buffer = buffer; + refcount_set(&umem->users, 1); if (ifobj->shared_umem && ifobj->rx_on) { umem->base_addr = umem_size(umem); umem->next_buffer = umem_size(umem); @@ -154,6 +155,7 @@ int xsk_configure_socket(struct xsk_socket_info *xsk, struct xsk_umem_info *umem struct xsk_socket_config cfg = {}; struct xsk_ring_cons *rxr; struct xsk_ring_prod *txr; + int ret; xsk->umem = umem; cfg.rx_size = xsk->rxqsize; @@ -170,7 +172,21 @@ int xsk_configure_socket(struct xsk_socket_info *xsk, struct xsk_umem_info *umem txr = ifobject->tx_on ? &xsk->tx : NULL; rxr = ifobject->rx_on ? &xsk->rx : NULL; - return xsk_socket__create(&xsk->xsk, ifobject->ifindex, 0, umem->umem, rxr, txr, &cfg); + ret = xsk_socket__create(&xsk->xsk, ifobject->ifindex, 0, umem->umem, rxr, txr, &cfg); + if (ret) { + if (shared) + /* Shared socket failed before acquiring a UMEM reference. */ + xsk->umem = NULL; + + /* Keep failed socket state inert for later cleanup paths. */ + xsk->xsk = NULL; + return ret; + } + + if (shared) + refcount_inc(&umem->users); + + return ret; } static int set_ring_size(struct ifobject *ifobj) @@ -1702,12 +1718,71 @@ void *worker_testapp_validate_rx(void *arg) pthread_exit(NULL); } -static void testapp_clean_xsk_umem(struct ifobject *ifobj) +void xsk_delete_socket(struct xsk_socket_info *xsk) { - struct xsk_umem_info *umem = ifobj->xsk->umem; + struct xsk_umem_info *umem; + + /* + * Callers must serialize teardown for a given xsk/umem pair. + * This helper does refcount-based lifetime management only. + */ + if (!xsk) + return; - xsk_umem__delete(umem->umem); - munmap(umem->buffer, umem->mmap_size); + umem = xsk->umem; + if (!umem) + return; + + /* Delete the socket handle when available, then drop UMEM reference. */ + if (xsk->xsk) + xsk_socket__delete(xsk->xsk); + xsk->xsk = NULL; + + /* Mark this socket as cleaned up to prevent double-decrement */ + xsk->umem = NULL; + + /* + * Teardown may walk the full xsk array, including slots that never completed setup but + * still carry a preinitialized umem pointer. After a partial-setup rollback, the last UMEM + * ref may already be dropped. + */ + if (!refcount_read(&umem->users)) + return; + + /* Always decrement refcount for this socket's UMEM reference */ + if (refcount_dec_and_test(&umem->users)) { + if (umem->umem) { + int err = xsk_umem__delete(umem->umem); + + if (err) + ksft_print_msg("xsk_umem__delete failed: %d (umem still busy?)\n", + err); + } + if (umem->buffer && umem->mmap_size) + munmap(umem->buffer, umem->mmap_size); + umem->umem = NULL; + umem->buffer = NULL; + umem->mmap_size = 0; + } +} + +static void xsk_delete_socket_batch(struct ifobject *ifobject, u32 count) +{ + u32 i; + + if (!ifobject) + return; + + for (i = count; i > 0; i--) + xsk_delete_socket(&ifobject->xsk_arr[i - 1]); +} + +static void xsk_delete_all_ifobj_sockets(struct test_spec *test, struct ifobject *ifobj) +{ + if (!ifobj) + return; + + xsk_delete_socket_batch(ifobj, test->nb_sockets); } static bool xdp_prog_changed_rx(struct test_spec *test) @@ -1769,27 +1844,6 @@ static int xsk_attach_xdp_progs(struct test_spec *test, struct ifobject *ifobj_r return err; } -static void clean_sockets(struct test_spec *test, struct ifobject *ifobj) -{ - u32 i; - - if (!ifobj || !test) - return; - - for (i = 0; i < test->nb_sockets; i++) - xsk_socket__delete(ifobj->xsk_arr[i].xsk); -} - -static void clean_umem(struct test_spec *test, struct ifobject *ifobj1, struct ifobject *ifobj2) -{ - if (!ifobj1) - return; - - testapp_clean_xsk_umem(ifobj1); - if (ifobj2 && !ifobj2->shared_umem) - testapp_clean_xsk_umem(ifobj2); -} - static int __testapp_validate_traffic(struct test_spec *test, struct ifobject *ifobj1, struct ifobject *ifobj2) { @@ -1841,8 +1895,7 @@ static int __testapp_validate_traffic(struct test_spec *test, struct ifobject *i if (pthread_barrier_destroy(&barr)) { test->use_barrier = false; pthread_join(t0, NULL); - clean_sockets(test, ifobj1); - clean_umem(test, ifobj1, NULL); + xsk_delete_all_ifobj_sockets(test, ifobj1); return TEST_FAILURE; } } @@ -1856,9 +1909,8 @@ static int __testapp_validate_traffic(struct test_spec *test, struct ifobject *i pthread_join(t0, NULL); if (test->total_steps == test->current_step || test->fail) { - clean_sockets(test, ifobj1); - clean_sockets(test, ifobj2); - clean_umem(test, ifobj1, ifobj2); + xsk_delete_all_ifobj_sockets(test, ifobj2); + xsk_delete_all_ifobj_sockets(test, ifobj1); } if (test->fail) @@ -1967,9 +2019,8 @@ int testapp_xdp_prog_cleanup(struct test_spec *test) return TEST_FAILURE; if (swap_xsk_resources(test)) { - clean_sockets(test, test->ifobj_rx); - clean_sockets(test, test->ifobj_tx); - clean_umem(test, test->ifobj_rx, test->ifobj_tx); + xsk_delete_all_ifobj_sockets(test, test->ifobj_tx); + xsk_delete_all_ifobj_sockets(test, test->ifobj_rx); return TEST_FAILURE; } @@ -2498,9 +2549,8 @@ int testapp_hw_sw_max_ring_size(struct test_spec *test) test->ifobj_tx->xsk->batch_size = test->ifobj_tx->ring.tx_max_pending - 8; test->ifobj_rx->xsk->batch_size = test->ifobj_tx->ring.tx_max_pending - 8; if (pkt_stream_replace(test, max_descs, MIN_PKT_SIZE)) { - clean_sockets(test, test->ifobj_tx); - clean_sockets(test, test->ifobj_rx); - clean_umem(test, test->ifobj_rx, test->ifobj_tx); + xsk_delete_all_ifobj_sockets(test, test->ifobj_tx); + xsk_delete_all_ifobj_sockets(test, test->ifobj_rx); return TEST_FAILURE; } diff --git a/tools/testing/selftests/bpf/prog_tests/test_xsk.h b/tools/testing/selftests/bpf/prog_tests/test_xsk.h index 03753ddc5dcd..56bc134505b3 100644 --- a/tools/testing/selftests/bpf/prog_tests/test_xsk.h +++ b/tools/testing/selftests/bpf/prog_tests/test_xsk.h @@ -4,6 +4,7 @@ #include #include +#include #include "../kselftest.h" #include "xsk.h" @@ -104,6 +105,7 @@ struct xsk_umem_info { struct xsk_umem *umem; u64 next_buffer; u64 mmap_size; + refcount_t users; u32 num_frames; u32 frame_headroom; void *buffer; @@ -159,6 +161,7 @@ int init_iface(struct ifobject *ifobj, thread_func_t func_ptr); int xsk_configure_umem(struct ifobject *ifobj, struct xsk_umem_info *umem, void *buffer, u64 size); int xsk_configure_socket(struct xsk_socket_info *xsk, struct xsk_umem_info *umem, struct ifobject *ifobject, bool shared); +void xsk_delete_socket(struct xsk_socket_info *xsk); struct pkt { diff --git a/tools/testing/selftests/bpf/xskxceiver.c b/tools/testing/selftests/bpf/xskxceiver.c index 7dad8556a722..ed7716b63756 100644 --- a/tools/testing/selftests/bpf/xskxceiver.c +++ b/tools/testing/selftests/bpf/xskxceiver.c @@ -117,12 +117,13 @@ static void __exit_with_error(int error, const char *file, const char *func, int #define exit_with_error(error) __exit_with_error(error, __FILE__, __func__, __LINE__) -static bool ifobj_zc_avail(struct ifobject *ifobject) +static bool ifobj_zc_avail(struct ifobject *ifobj) { size_t umem_sz = DEFAULT_UMEM_BUFFERS * XSK_UMEM__DEFAULT_FRAME_SIZE; int mmap_flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE; - struct xsk_socket_info *xsk; - struct xsk_umem_info *umem; + struct xsk_socket_info xsk = {}; + struct xsk_umem_info umem_info = {}; + struct xsk_umem_info *umem = &umem_info; bool zc_avail = false; void *bufs; int ret; @@ -131,32 +132,22 @@ static bool ifobj_zc_avail(struct ifobject *ifobject) if (bufs == MAP_FAILED) exit_with_error(errno); - umem = calloc(1, sizeof(struct xsk_umem_info)); - if (!umem) { - munmap(bufs, umem_sz); - exit_with_error(ENOMEM); - } + umem->mmap_size = umem_sz; umem->frame_size = XSK_UMEM__DEFAULT_FRAME_SIZE; - ret = xsk_configure_umem(ifobject, umem, bufs, umem_sz); + ret = xsk_configure_umem(ifobj, umem, bufs, umem_sz); if (ret) exit_with_error(-ret); - xsk = calloc(1, sizeof(struct xsk_socket_info)); - if (!xsk) - goto out; - ifobject->bind_flags = XDP_USE_NEED_WAKEUP | XDP_ZEROCOPY; - ifobject->rx_on = true; - xsk->rxqsize = XSK_RING_CONS__DEFAULT_NUM_DESCS; - ret = xsk_configure_socket(xsk, umem, ifobject, false); + ifobj->bind_flags = XDP_USE_NEED_WAKEUP | XDP_ZEROCOPY; + ifobj->rx_on = true; + xsk.rxqsize = XSK_RING_CONS__DEFAULT_NUM_DESCS; + ret = xsk_configure_socket(&xsk, umem, ifobj, false); if (!ret) zc_avail = true; - xsk_socket__delete(xsk->xsk); - free(xsk); -out: - munmap(umem->buffer, umem_sz); - xsk_umem__delete(umem->umem); - free(umem); + /* Use the same refcount-based teardown path for both success and failure. */ + xsk_delete_socket(&xsk); + return zc_avail; } -- 2.43.0 When xsk_socket__create() fails after all retries, or when busy-poll setup fails after the socket is created, already-configured sockets for the same ifobject are leaked. Add xsk_configure_rollback() to call xsk_delete_socket_batch() on all sockets configured so far, and wire it into both failure paths in xsk_configure(). As part of this change, move xsk_delete_socket_batch() next to xsk_configure_rollback() so rollback helpers are grouped with xsk_configure()-local failure handling. Signed-off-by: Magnus Karlsson Signed-off-by: Tushar Vyavahare --- .../selftests/bpf/prog_tests/test_xsk.c | 41 +++++++++++++------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/tools/testing/selftests/bpf/prog_tests/test_xsk.c b/tools/testing/selftests/bpf/prog_tests/test_xsk.c index 4ccdb0825130..de0d8f51f846 100644 --- a/tools/testing/selftests/bpf/prog_tests/test_xsk.c +++ b/tools/testing/selftests/bpf/prog_tests/test_xsk.c @@ -1484,6 +1484,28 @@ static int validate_tx_invalid_descs(struct ifobject *ifobject) return TEST_PASS; } +static void xsk_delete_socket_batch(struct ifobject *ifobject, u32 count) +{ + u32 i; + + if (!ifobject) + return; + + for (i = count; i > 0; i--) + xsk_delete_socket(&ifobject->xsk_arr[i - 1]); +} + +static void xsk_configure_rollback(struct ifobject *ifobject, int last) +{ + u32 count; + + if (!ifobject || last < 0) + return; + + count = last + 1; + xsk_delete_socket_batch(ifobject, count); +} + static int xsk_configure(struct test_spec *test, struct ifobject *ifobject, struct xsk_umem_info *umem, bool tx) { @@ -1500,14 +1522,18 @@ static int xsk_configure(struct test_spec *test, struct ifobject *ifobject, break; /* Retry if it fails as xsk_socket__create() is asynchronous */ - if (ctr >= SOCK_RECONF_CTR) + if (ctr >= SOCK_RECONF_CTR) { + xsk_configure_rollback(ifobject, i); return ret; + } usleep(USLEEP_MAX); } if (ifobject->busy_poll) { ret = enable_busy_poll(&ifobject->xsk_arr[i]); - if (ret) + if (ret) { + xsk_configure_rollback(ifobject, i); return ret; + } } } @@ -1766,17 +1792,6 @@ void xsk_delete_socket(struct xsk_socket_info *xsk) } } -static void xsk_delete_socket_batch(struct ifobject *ifobject, u32 count) -{ - u32 i; - - if (!ifobject) - return; - - for (i = count; i > 0; i--) - xsk_delete_socket(&ifobject->xsk_arr[i - 1]); -} - static void xsk_delete_all_ifobj_sockets(struct test_spec *test, struct ifobject *ifobj) { if (!ifobj) -- 2.43.0 Raise MAX_SOCKETS from 2 to 4 so the XSKMAP and test arrays can accommodate the upcoming 4-socket shared-UMEM test. Update the XSKMAP max_entries to use MAX_SOCKETS so the BPF and C sides stay in sync. Add xsk_xdp_shared_umem_length_based() XDP program that routes packets by total XDP-visible packet length (data_end - data): socket 0 for packets <= SHARED_UMEM_LEN_SPLIT bytes and socket 1 for packets > SHARED_UMEM_LEN_SPLIT bytes. Define SHARED_UMEM_LEN_SPLIT as 64 to avoid hardcoded length thresholds in the classifier. Signed-off-by: Magnus Karlsson Signed-off-by: Tushar Vyavahare --- .../selftests/bpf/progs/xsk_xdp_progs.c | 19 ++++++++++++++++++- tools/testing/selftests/bpf/xsk_xdp_common.h | 3 ++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/bpf/progs/xsk_xdp_progs.c b/tools/testing/selftests/bpf/progs/xsk_xdp_progs.c index 023d8befd4ca..543b61df3a2b 100644 --- a/tools/testing/selftests/bpf/progs/xsk_xdp_progs.c +++ b/tools/testing/selftests/bpf/progs/xsk_xdp_progs.c @@ -10,7 +10,7 @@ struct { __uint(type, BPF_MAP_TYPE_XSKMAP); - __uint(max_entries, 2); + __uint(max_entries, MAX_SOCKETS); __uint(key_size, sizeof(int)); __uint(value_size, sizeof(int)); } xsk SEC(".maps"); @@ -75,6 +75,23 @@ SEC("xdp") int xsk_xdp_shared_umem(struct xdp_md *xdp) return bpf_redirect_map(&xsk, idx, XDP_DROP); } +SEC("xdp") int xsk_xdp_shared_umem_length_based(struct xdp_md *xdp) +{ + void *data = (void *)(long)xdp->data; + void *data_end = (void *)(long)xdp->data_end; + __u32 pkt_len = data_end - data; + + /* + * Route packets by total XDP-visible packet length (data_end - data): + * - Socket 0: packets <= SHARED_UMEM_LEN_SPLIT bytes + * - Socket 1: packets > SHARED_UMEM_LEN_SPLIT bytes + */ + if (pkt_len <= SHARED_UMEM_LEN_SPLIT) + return bpf_redirect_map(&xsk, 0, XDP_DROP); + else + return bpf_redirect_map(&xsk, 1, XDP_DROP); +} + SEC("xdp.frags") int xsk_xdp_adjust_tail(struct xdp_md *xdp) { __u32 buff_len, curr_buff_len; diff --git a/tools/testing/selftests/bpf/xsk_xdp_common.h b/tools/testing/selftests/bpf/xsk_xdp_common.h index 45810ff552da..bd9b5dcf8c8e 100644 --- a/tools/testing/selftests/bpf/xsk_xdp_common.h +++ b/tools/testing/selftests/bpf/xsk_xdp_common.h @@ -3,7 +3,8 @@ #ifndef XSK_XDP_COMMON_H_ #define XSK_XDP_COMMON_H_ -#define MAX_SOCKETS 2 +#define MAX_SOCKETS 4 +#define SHARED_UMEM_LEN_SPLIT 64 #define PKT_HDR_ALIGN (sizeof(struct ethhdr) + 2) /* Just to align the data in the packet */ struct xdp_info { -- 2.43.0 Add the runner callback infrastructure and the first four shared-UMEM test cases together so every SHA in the series builds clean. Framework: - shared_default in __test_spec_init() derives the shared_umem flag from ifindex equality, matching xskxceiver startup behavior so test resets stay aligned with the harness invariant. - pkt_stream_len_seq() generates per-socket streams with alternating short/long packet sizes. - pkt_stream_weighted_uneven_dist_sequence() generates unequal-volume streams across two sockets. - run_shared_umem_test() is a common runner: sets up the XDP program, validates streams are ready, runs the sequence callback, executes traffic, and calls an optional post-run callback. - Callback types shared_umem_seq_fn and shared_umem_post_fn, and context structs shared_umem_len_ctx and shared_umem_uneven_dist_ctx are defined in test_xsk.h for shared use. Tests: - SHARED_UMEM_4_SOCKETS: runs four sockets simultaneously with an even/odd packet stream split across sockets. - SHARED_UMEM_LENGTH_BASED: routes short packets to socket 0 and longer packets to socket 1 using the length-based XDP program. - SHARED_UMEM_UNEVEN_DIST: generates a 1:3 volume split across two sockets and validates that socket 1 receives more packets. - SHARED_UMEM_UNALIGNED: runs the even/odd split in unaligned chunk mode. The unaligned test snapshots and restores prior alignment flags with NULL-safe UMEM guards so it does not depend on test-order side effects or implicit initialization assumptions in the callback. Signed-off-by: Magnus Karlsson Signed-off-by: Tushar Vyavahare --- .../selftests/bpf/prog_tests/test_xsk.c | 250 ++++++++++++++++++ .../selftests/bpf/prog_tests/test_xsk.h | 21 ++ 2 files changed, 271 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/test_xsk.c b/tools/testing/selftests/bpf/prog_tests/test_xsk.c index de0d8f51f846..1665339844b5 100644 --- a/tools/testing/selftests/bpf/prog_tests/test_xsk.c +++ b/tools/testing/selftests/bpf/prog_tests/test_xsk.c @@ -219,6 +219,12 @@ int hw_ring_size_reset(struct ifobject *ifobj) static void __test_spec_init(struct test_spec *test, struct ifobject *ifobj_tx, struct ifobject *ifobj_rx) { + /* + * Keep the same default as xskxceiver startup: when TX and RX share the same netdev, + * shared UMEM is the baseline mode for this test harness. Individual tests can still + * override this as needed. + */ + bool shared_default = ifobj_tx->ifindex == ifobj_rx->ifindex; u32 i, j; for (i = 0; i < MAX_INTERFACES; i++) { @@ -230,6 +236,7 @@ static void __test_spec_init(struct test_spec *test, struct ifobject *ifobj_tx, ifobj->use_fill_ring = true; ifobj->release_rx = true; ifobj->validation_func = NULL; + ifobj->shared_umem = shared_default; ifobj->use_metadata = false; if (i == 0) { @@ -612,6 +619,87 @@ static int pkt_stream_even_odd_sequence(struct test_spec *test) return 0; } +static int pkt_stream_len_seq(struct test_spec *test, u32 short_len, u32 long_len) +{ + struct pkt_stream *tx_streams[MAX_SOCKETS] = {}; + struct pkt_stream *rx_streams[MAX_SOCKETS] = {}; + struct pkt_stream *pkt_stream; + u32 i; + + for (i = 0; i < test->nb_sockets; i++) { + u32 pkt_len = i ? long_len : short_len; + + pkt_stream = test->ifobj_tx->xsk_arr[i].pkt_stream; + tx_streams[i] = __pkt_stream_generate(pkt_stream->nb_pkts / 2, pkt_len, i, 2); + if (!tx_streams[i]) + goto err; + + pkt_stream = test->ifobj_rx->xsk_arr[i].pkt_stream; + rx_streams[i] = __pkt_stream_generate(pkt_stream->nb_pkts / 2, pkt_len, i, 2); + if (!rx_streams[i]) + goto err; + } + + for (i = 0; i < test->nb_sockets; i++) { + test->ifobj_tx->xsk_arr[i].pkt_stream = tx_streams[i]; + test->ifobj_rx->xsk_arr[i].pkt_stream = rx_streams[i]; + } + + return 0; + +err: + for (i = 0; i < test->nb_sockets; i++) { + if (tx_streams[i]) + pkt_stream_delete(tx_streams[i]); + if (rx_streams[i]) + pkt_stream_delete(rx_streams[i]); + } + + return -ENOMEM; +} + +static int pkt_stream_weighted_uneven_dist_sequence(struct test_spec *test, u32 total_pkts, + u32 pkt_len) +{ + struct pkt_stream *tx_streams[MAX_SOCKETS] = {}; + struct pkt_stream *rx_streams[MAX_SOCKETS] = {}; + u32 i, pkts_sock0; + + if (test->nb_sockets < 2 || total_pkts < 4) + return -EINVAL; + + pkts_sock0 = total_pkts / 4; + + for (i = 0; i < test->nb_sockets; i++) { + u32 nb_pkts = (i == 0) ? pkts_sock0 : (total_pkts - pkts_sock0); + + tx_streams[i] = __pkt_stream_generate(nb_pkts, pkt_len, i, 2); + if (!tx_streams[i]) + goto err; + + rx_streams[i] = __pkt_stream_generate(nb_pkts, pkt_len, i, 2); + if (!rx_streams[i]) + goto err; + } + + for (i = 0; i < test->nb_sockets; i++) { + test->ifobj_tx->xsk_arr[i].pkt_stream = tx_streams[i]; + test->ifobj_rx->xsk_arr[i].pkt_stream = rx_streams[i]; + } + + return 0; + +err: + for (i = 0; i < test->nb_sockets; i++) { + if (tx_streams[i]) + pkt_stream_delete(tx_streams[i]); + if (rx_streams[i]) + pkt_stream_delete(rx_streams[i]); + } + + return -ENOMEM; +} + static void release_even_odd_sequence(struct test_spec *test) { struct pkt_stream *later_free_tx = test->ifobj_tx->xsk->pkt_stream; @@ -2290,6 +2378,168 @@ int testapp_xdp_shared_umem(struct test_spec *test) return ret; } +static int shared_umem_test_prepare(struct test_spec *test) +{ + u32 i; + + if (test->nb_sockets > MAX_SOCKETS) { + ksft_print_msg("ERROR: [%s] invalid socket count %u\n", __func__, test->nb_sockets); + return TEST_FAILURE; + } + + for (i = 0; i < test->nb_sockets; i++) { + if (!test->ifobj_rx->xsk_arr[i].pkt_stream || + !test->ifobj_tx->xsk_arr[i].pkt_stream) { + ksft_print_msg("ERROR: [%s] missing stream for socket %u\n", __func__, i); + return TEST_FAILURE; + } + } + + return TEST_PASS; +} + +static int shared_umem_seq_even_odd(struct test_spec *test, const void *ctx) +{ + (void)ctx; + + return pkt_stream_even_odd_sequence(test) ? TEST_FAILURE : TEST_PASS; +} + +static int shared_umem_seq_len(struct test_spec *test, const void *ctx) +{ + const struct shared_umem_len_ctx *cfg = ctx; + + return pkt_stream_len_seq(test, cfg->short_len, cfg->long_len) ? TEST_FAILURE : TEST_PASS; +} + +static int shared_umem_seq_uneven_dist(struct test_spec *test, const void *ctx) +{ + const struct shared_umem_uneven_dist_ctx *cfg = ctx; + + return pkt_stream_weighted_uneven_dist_sequence(test, cfg->total_pkts, + cfg->pkt_len) ? TEST_FAILURE : TEST_PASS; +} + +static int shared_umem_post_uneven_dist(struct test_spec *test, int ret, const void *ctx) +{ + struct pkt_stream *tx_stream_0, *tx_stream_1; + struct pkt_stream *rx_stream_0, *rx_stream_1; + + (void)ctx; + + tx_stream_0 = test->ifobj_tx->xsk_arr[0].pkt_stream; + tx_stream_1 = test->ifobj_tx->xsk_arr[1].pkt_stream; + rx_stream_0 = test->ifobj_rx->xsk_arr[0].pkt_stream; + rx_stream_1 = test->ifobj_rx->xsk_arr[1].pkt_stream; + + if (tx_stream_1->nb_valid_entries <= tx_stream_0->nb_valid_entries) + return TEST_FAILURE; + + if (!ret && rx_stream_1->nb_rx_pkts <= rx_stream_0->nb_rx_pkts) { + ksft_print_msg("ERROR: socket1 rx_pkts (%u) not greater than socket0 (%u)\n", + rx_stream_1->nb_rx_pkts, rx_stream_0->nb_rx_pkts); + ret = TEST_FAILURE; + } + + return ret; +} + +static int run_shared_umem_test(struct test_spec *test, struct bpf_program *xdp_prog_rx, + struct bpf_program *xdp_prog_tx, struct bpf_map *xskmap_rx, + struct bpf_map *xskmap_tx, u32 nb_sockets, + shared_umem_seq_fn seq_fn, shared_umem_post_fn post_fn, + const void *ctx) +{ + int ret; + + test->total_steps = 1; + test->nb_sockets = nb_sockets; + + test_spec_set_xdp_prog(test, xdp_prog_rx, xdp_prog_tx, xskmap_rx, xskmap_tx); + + ret = shared_umem_test_prepare(test); + if (ret) + return ret; + + ret = seq_fn(test, ctx); + if (ret) + return ret; + + ret = testapp_validate_traffic(test); + if (post_fn) + ret = post_fn(test, ret, ctx); + + release_even_odd_sequence(test); + + return ret; +} + +int testapp_shared_umem_4_sockets(struct test_spec *test) +{ + struct xsk_xdp_progs *skel_rx = test->ifobj_rx->xdp_progs; + struct xsk_xdp_progs *skel_tx = test->ifobj_tx->xdp_progs; + + return run_shared_umem_test(test, skel_rx->progs.xsk_xdp_shared_umem, + skel_tx->progs.xsk_xdp_shared_umem, skel_rx->maps.xsk, + skel_tx->maps.xsk, 4, shared_umem_seq_even_odd, NULL, NULL); +} + +int testapp_shared_umem_length_based(struct test_spec *test) +{ + struct xsk_xdp_progs *skel_rx = test->ifobj_rx->xdp_progs; + struct xsk_xdp_progs *skel_tx = test->ifobj_tx->xdp_progs; + const struct shared_umem_len_ctx len_ctx = { + .short_len = MIN_PKT_SIZE, + .long_len = MIN_PKT_SIZE * 2, + }; + + return run_shared_umem_test(test, skel_rx->progs.xsk_xdp_shared_umem_length_based, + skel_tx->progs.xsk_xdp_shared_umem_length_based, + skel_rx->maps.xsk, skel_tx->maps.xsk, 2, shared_umem_seq_len, + NULL, &len_ctx); +} + +int testapp_shared_umem_uneven_dist(struct test_spec *test) +{ + struct xsk_xdp_progs *skel_rx = test->ifobj_rx->xdp_progs; + struct xsk_xdp_progs *skel_tx = test->ifobj_tx->xdp_progs; + const struct shared_umem_uneven_dist_ctx uneven_dist_ctx = { + .total_pkts = DEFAULT_PKT_CNT * 4, + .pkt_len = MIN_PKT_SIZE, + }; + + return run_shared_umem_test(test, skel_rx->progs.xsk_xdp_shared_umem, + skel_tx->progs.xsk_xdp_shared_umem, skel_rx->maps.xsk, + skel_tx->maps.xsk, 2, shared_umem_seq_uneven_dist, + shared_umem_post_uneven_dist, &uneven_dist_ctx); +} + +int testapp_shared_umem_unaligned(struct test_spec *test) +{ + struct xsk_xdp_progs *skel_rx = test->ifobj_rx->xdp_progs; + struct xsk_xdp_progs *skel_tx = test->ifobj_tx->xdp_progs; + struct xsk_umem_info *tx_umem = test->ifobj_tx && test->ifobj_tx->xsk ? + test->ifobj_tx->xsk->umem : NULL; + struct xsk_umem_info *rx_umem = test->ifobj_rx && test->ifobj_rx->xsk ? + test->ifobj_rx->xsk->umem : NULL; + bool tx_unaligned = tx_umem ? tx_umem->unaligned_mode : false; + bool rx_unaligned = rx_umem ? rx_umem->unaligned_mode : false; + int ret; + + test_spec_set_unaligned(test); + + ret = run_shared_umem_test(test, skel_rx->progs.xsk_xdp_shared_umem, + skel_tx->progs.xsk_xdp_shared_umem, skel_rx->maps.xsk, + skel_tx->maps.xsk, 2, shared_umem_seq_even_odd, NULL, NULL); + + if (tx_umem) + tx_umem->unaligned_mode = tx_unaligned; + if (rx_umem) + rx_umem->unaligned_mode = rx_unaligned; + + return ret; +} + int testapp_poll_txq_tmout(struct test_spec *test) { bool shared_umem = test->ifobj_tx->shared_umem; diff --git a/tools/testing/selftests/bpf/prog_tests/test_xsk.h b/tools/testing/selftests/bpf/prog_tests/test_xsk.h index 56bc134505b3..ef9c422d7763 100644 --- a/tools/testing/selftests/bpf/prog_tests/test_xsk.h +++ b/tools/testing/selftests/bpf/prog_tests/test_xsk.h @@ -80,6 +80,9 @@ struct test_spec; typedef int (*validation_func_t)(struct ifobject *ifobj); typedef void *(*thread_func_t)(void *arg); typedef int (*test_func_t)(struct test_spec *test); +typedef int (*shared_umem_seq_fn)(struct test_spec *test, const void *ctx); +typedef int (*shared_umem_post_fn)(struct test_spec *test, int ret, + const void *ctx); struct xsk_socket_info { struct xsk_ring_cons rx; @@ -182,6 +185,16 @@ struct pkt_stream { bool verbatim; }; +struct shared_umem_len_ctx { + u32 short_len; + u32 long_len; +}; + +struct shared_umem_uneven_dist_ctx { + u32 total_pkts; + u32 pkt_len; +}; + static inline bool pkt_continues(u32 options) { return options & XDP_PKT_CONTD; @@ -271,6 +284,10 @@ int testapp_xdp_metadata(struct test_spec *test); int testapp_xdp_metadata_mb(struct test_spec *test); int testapp_xdp_prog_cleanup(struct test_spec *test); int testapp_xdp_shared_umem(struct test_spec *test); +int testapp_shared_umem_4_sockets(struct test_spec *test); +int testapp_shared_umem_length_based(struct test_spec *test); +int testapp_shared_umem_uneven_dist(struct test_spec *test); +int testapp_shared_umem_unaligned(struct test_spec *test); void *worker_testapp_validate_rx(void *arg); void *worker_testapp_validate_tx(void *arg); @@ -294,6 +311,10 @@ static const struct test_spec tests[] = { {.name = "XDP_PROG_CLEANUP", .test_func = testapp_xdp_prog_cleanup}, {.name = "XDP_DROP_HALF", .test_func = testapp_xdp_drop}, {.name = "XDP_SHARED_UMEM", .test_func = testapp_xdp_shared_umem}, + {.name = "SHARED_UMEM_4_SOCKETS", .test_func = testapp_shared_umem_4_sockets}, + {.name = "SHARED_UMEM_LENGTH_BASED", .test_func = testapp_shared_umem_length_based}, + {.name = "SHARED_UMEM_UNEVEN_DIST", .test_func = testapp_shared_umem_uneven_dist}, + {.name = "SHARED_UMEM_UNALIGNED", .test_func = testapp_shared_umem_unaligned}, {.name = "XDP_METADATA_COPY", .test_func = testapp_xdp_metadata}, {.name = "XDP_METADATA_COPY_MULTI_BUFF", .test_func = testapp_xdp_metadata_mb}, {.name = "ALIGNED_INV_DESC_MULTI_BUFF", .test_func = testapp_aligned_inv_desc_mb}, -- 2.43.0 If __pkt_stream_generate() fails midway through building per-socket streams, the original code overwrites xsk_arr[i].pkt_stream with a partial or NULL pointer before all allocations complete. Allocate all TX and RX streams into temporary arrays first and only assign them to xsk_arr after every allocation succeeds. On any failure free the already-allocated temporaries and return -ENOMEM with no xsk_arr pointers modified. Signed-off-by: Magnus Karlsson Signed-off-by: Tushar Vyavahare --- .../selftests/bpf/prog_tests/test_xsk.c | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/tools/testing/selftests/bpf/prog_tests/test_xsk.c b/tools/testing/selftests/bpf/prog_tests/test_xsk.c index 1665339844b5..ccfd9e8436a7 100644 --- a/tools/testing/selftests/bpf/prog_tests/test_xsk.c +++ b/tools/testing/selftests/bpf/prog_tests/test_xsk.c @@ -597,26 +597,41 @@ static int pkt_stream_receive_half(struct test_spec *test) static int pkt_stream_even_odd_sequence(struct test_spec *test) { + struct pkt_stream *tx_streams[MAX_SOCKETS] = {}; + struct pkt_stream *rx_streams[MAX_SOCKETS] = {}; struct pkt_stream *pkt_stream; u32 i; for (i = 0; i < test->nb_sockets; i++) { pkt_stream = test->ifobj_tx->xsk_arr[i].pkt_stream; - pkt_stream = __pkt_stream_generate(pkt_stream->nb_pkts / 2, - pkt_stream->pkts[0].len, i, 2); - if (!pkt_stream) - return -ENOMEM; - test->ifobj_tx->xsk_arr[i].pkt_stream = pkt_stream; + tx_streams[i] = __pkt_stream_generate(pkt_stream->nb_pkts / 2, + pkt_stream->pkts[0].len, i, 2); + if (!tx_streams[i]) + goto err; pkt_stream = test->ifobj_rx->xsk_arr[i].pkt_stream; - pkt_stream = __pkt_stream_generate(pkt_stream->nb_pkts / 2, - pkt_stream->pkts[0].len, i, 2); - if (!pkt_stream) - return -ENOMEM; - test->ifobj_rx->xsk_arr[i].pkt_stream = pkt_stream; + rx_streams[i] = __pkt_stream_generate(pkt_stream->nb_pkts / 2, + pkt_stream->pkts[0].len, i, 2); + if (!rx_streams[i]) + goto err; + } + + for (i = 0; i < test->nb_sockets; i++) { + test->ifobj_tx->xsk_arr[i].pkt_stream = tx_streams[i]; + test->ifobj_rx->xsk_arr[i].pkt_stream = rx_streams[i]; } return 0; + +err: + for (i = 0; i < test->nb_sockets; i++) { + if (tx_streams[i]) + pkt_stream_delete(tx_streams[i]); + if (rx_streams[i]) + pkt_stream_delete(rx_streams[i]); + } + + return -ENOMEM; } static int pkt_stream_len_seq(struct test_spec *test, u32 short_len, u32 long_len) -- 2.43.0