This adds tests for zerocopy feature: one test checks data transmission with simple integrity control. Second test covers 'error' branches in zerocopy logic(to check invalid arguments handling). Signed-off-by: Arseniy Krasnov <AVKrasnov@xxxxxxxxxxxxxx> --- tools/include/uapi/linux/virtio_vsock.h | 15 + tools/include/uapi/linux/vm_sockets.h | 8 + tools/testing/vsock/Makefile | 2 +- tools/testing/vsock/util.c | 27 +- tools/testing/vsock/util.h | 4 + tools/testing/vsock/vsock_test.c | 21 + tools/testing/vsock/vsock_test_zerocopy.c | 530 ++++++++++++++++++++++ tools/testing/vsock/vsock_test_zerocopy.h | 14 + 8 files changed, 617 insertions(+), 4 deletions(-) create mode 100644 tools/include/uapi/linux/virtio_vsock.h create mode 100644 tools/include/uapi/linux/vm_sockets.h create mode 100644 tools/testing/vsock/vsock_test_zerocopy.c create mode 100644 tools/testing/vsock/vsock_test_zerocopy.h diff --git a/tools/include/uapi/linux/virtio_vsock.h b/tools/include/uapi/linux/virtio_vsock.h new file mode 100644 index 000000000000..f393062e0394 --- /dev/null +++ b/tools/include/uapi/linux/virtio_vsock.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _UAPI_LINUX_VIRTIO_VSOCK_H +#define _UAPI_LINUX_VIRTIO_VSOCK_H +#include <linux/types.h> + +struct virtio_vsock_usr_hdr { + u32 flags; + u32 len; +} __attribute__((packed)); + +struct virtio_vsock_usr_hdr_pref { + u32 poll_value; + u32 hdr_num; +} __attribute__((packed)); +#endif /* _UAPI_LINUX_VIRTIO_VSOCK_H */ diff --git a/tools/include/uapi/linux/vm_sockets.h b/tools/include/uapi/linux/vm_sockets.h new file mode 100644 index 000000000000..cac0bc3a7041 --- /dev/null +++ b/tools/include/uapi/linux/vm_sockets.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _UAPI_LINUX_VM_SOCKETS_H +#define _UAPI_LINUX_VM_SOCKETS_H + +#define SO_VM_SOCKETS_MAP_RX 9 +#define SO_VM_SOCKETS_ZEROCOPY 10 + +#endif /* _UAPI_LINUX_VM_SOCKETS_H */ diff --git a/tools/testing/vsock/Makefile b/tools/testing/vsock/Makefile index f8293c6910c9..7172c21fbd8d 100644 --- a/tools/testing/vsock/Makefile +++ b/tools/testing/vsock/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only all: test test: vsock_test vsock_diag_test -vsock_test: vsock_test.o timeout.o control.o util.o +vsock_test: vsock_test.o vsock_test_zerocopy.o timeout.o control.o util.o vsock_diag_test: vsock_diag_test.o timeout.o control.o util.o CFLAGS += -g -O2 -Werror -Wall -I. -I../../include -I../../../usr/include -Wno-pointer-sign -fno-strict-overflow -fno-strict-aliasing -fno-common -MMD -U_FORTIFY_SOURCE -D_GNU_SOURCE diff --git a/tools/testing/vsock/util.c b/tools/testing/vsock/util.c index 351903836774..7ae5219d0fe8 100644 --- a/tools/testing/vsock/util.c +++ b/tools/testing/vsock/util.c @@ -84,7 +84,8 @@ void vsock_wait_remote_close(int fd) } /* Connect to <cid, port> and return the file descriptor. */ -static int vsock_connect(unsigned int cid, unsigned int port, int type) +static int vsock_connect(unsigned int cid, unsigned int port, int type, + int optname, void *optval, socklen_t optlen) { union { struct sockaddr sa; @@ -103,6 +104,13 @@ static int vsock_connect(unsigned int cid, unsigned int port, int type) fd = socket(AF_VSOCK, type, 0); + if (optval) { + if (setsockopt(fd, AF_VSOCK, optname, optval, optlen)) { + close(fd); + return -1; + } + } + timeout_begin(TIMEOUT); do { ret = connect(fd, &addr.sa, sizeof(addr.svm)); @@ -122,12 +130,25 @@ static int vsock_connect(unsigned int cid, unsigned int port, int type) int vsock_stream_connect(unsigned int cid, unsigned int port) { - return vsock_connect(cid, port, SOCK_STREAM); + return vsock_connect(cid, port, SOCK_STREAM, 0, NULL, 0); +} + +int vsock_stream_connect_opt(unsigned int cid, unsigned int port, + int optname, void *optval, socklen_t optlen) +{ + return vsock_connect(cid, port, SOCK_STREAM, optname, optval, optlen); } int vsock_seqpacket_connect(unsigned int cid, unsigned int port) { - return vsock_connect(cid, port, SOCK_SEQPACKET); + return vsock_connect(cid, port, SOCK_SEQPACKET, 0, NULL, 0); +} + +int vsock_seqpacket_connect_opt(unsigned int cid, unsigned int port, + int optname, void *optval, socklen_t optlen) +{ + return vsock_connect(cid, port, SOCK_SEQPACKET, optname, optval, + optlen); } /* Listen on <cid, port> and return the first incoming connection. The remote diff --git a/tools/testing/vsock/util.h b/tools/testing/vsock/util.h index 988cc69a4642..23f9f0c6853d 100644 --- a/tools/testing/vsock/util.h +++ b/tools/testing/vsock/util.h @@ -36,7 +36,11 @@ struct test_case { void init_signals(void); unsigned int parse_cid(const char *str); int vsock_stream_connect(unsigned int cid, unsigned int port); +int vsock_stream_connect_opt(unsigned int cid, unsigned int port, + int optname, void *optval, socklen_t optlen); int vsock_seqpacket_connect(unsigned int cid, unsigned int port); +int vsock_seqpacket_connect_opt(unsigned int cid, unsigned int port, + int optname, void *optval, socklen_t optlen); int vsock_stream_accept(unsigned int cid, unsigned int port, struct sockaddr_vm *clientaddrp); int vsock_seqpacket_accept(unsigned int cid, unsigned int port, diff --git a/tools/testing/vsock/vsock_test.c b/tools/testing/vsock/vsock_test.c index bb4e8657f1d6..a6ed076e42f4 100644 --- a/tools/testing/vsock/vsock_test.c +++ b/tools/testing/vsock/vsock_test.c @@ -23,6 +23,7 @@ #include "timeout.h" #include "control.h" #include "util.h" +#include "vsock_test_zerocopy.h" static void test_stream_connection_reset(const struct test_opts *opts) { @@ -904,6 +905,26 @@ static struct test_case test_cases[] = { .run_client = test_stream_poll_rcvlowat_client, .run_server = test_stream_poll_rcvlowat_server, }, + { + .name = "SOCK_STREAM zerocopy receive", + .run_client = test_stream_zerocopy_rx_client, + .run_server = test_stream_zerocopy_rx_server, + }, + { + .name = "SOCK_SEQPACKET zerocopy receive", + .run_client = test_seqpacket_zerocopy_rx_client, + .run_server = test_seqpacket_zerocopy_rx_server, + }, + { + .name = "SOCK_STREAM zerocopy receive loop poll", + .run_client = test_stream_zerocopy_rx_client_loop_poll, + .run_server = test_stream_zerocopy_rx_server_loop_poll, + }, + { + .name = "SOCK_STREAM zerocopy invalid", + .run_client = test_stream_zerocopy_rx_inv_client, + .run_server = test_stream_zerocopy_rx_inv_server, + }, {}, }; diff --git a/tools/testing/vsock/vsock_test_zerocopy.c b/tools/testing/vsock/vsock_test_zerocopy.c new file mode 100644 index 000000000000..1876f14c0cec --- /dev/null +++ b/tools/testing/vsock/vsock_test_zerocopy.c @@ -0,0 +1,530 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * + * Copyright (C) 2022 Sberdevices, Inc. + * + * Author: Arseniy Krasnov <AVKrasnov@xxxxxxxxxxxxxx> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <uapi/linux/virtio_vsock.h> +#include <uapi/linux/vm_sockets.h> +#include <sys/mman.h> +#include <poll.h> +#include <unistd.h> + +#include "timeout.h" +#include "control.h" +#include "util.h" +#include "vsock_test_zerocopy.h" + +#define PAGE_SIZE 4096 +#define RX_MAPPING_PAGES 3 + +#define TX_BUF_SIZE 40000 +#define TX_SEND_LOOPS 3 + +static void test_connectible_zerocopy_rx_client(const struct test_opts *opts, + bool stream) +{ + unsigned long remote_hash; + unsigned long curr_hash; + unsigned long total_sum; + unsigned long msg_bytes; + unsigned long zc_on; + size_t rx_map_len; + void *rx_va; + int fd; + + zc_on = 1; + + if (stream) + fd = vsock_stream_connect_opt(opts->peer_cid, 1234, + SO_VM_SOCKETS_ZEROCOPY, + (void *)&zc_on, sizeof(zc_on)); + else + fd = vsock_seqpacket_connect_opt(opts->peer_cid, 1234, + SO_VM_SOCKETS_ZEROCOPY, + (void *)&zc_on, sizeof(zc_on)); + if (fd < 0) { + perror("connect"); + exit(EXIT_FAILURE); + } + + rx_map_len = PAGE_SIZE * RX_MAPPING_PAGES; + + rx_va = mmap(NULL, rx_map_len, PROT_READ, MAP_SHARED, fd, 0); + if (rx_va == MAP_FAILED) { + perror("mmap"); + exit(EXIT_FAILURE); + } + + total_sum = 0; + msg_bytes = 0; + curr_hash = 0; + + while (1) { + struct pollfd fds = { 0 }; + int leave_loop; + int res; + + fds.fd = fd; + fds.events = POLLIN | POLLERR | POLLHUP | + POLLRDHUP | POLLNVAL; + + res = poll(&fds, 1, -1); + + if (res < 0) { + perror("poll"); + exit(EXIT_FAILURE); + } + + if (fds.revents & POLLERR) { + perror("poll error"); + exit(EXIT_FAILURE); + } + + leave_loop = 0; + + if (fds.revents & POLLIN) { + struct virtio_vsock_usr_hdr_pref *poll_hdr; + struct virtio_vsock_usr_hdr *data_hdr; + unsigned char *data_va; + unsigned char *end_va; + socklen_t len; + int hdr_cnt; + + poll_hdr = (struct virtio_vsock_usr_hdr_pref *)rx_va; + len = sizeof(rx_va); + + if (getsockopt(fd, AF_VSOCK, + SO_VM_SOCKETS_MAP_RX, + &rx_va, &len) < 0) { + perror("getsockopt"); + exit(EXIT_FAILURE); + } + + data_hdr = (struct virtio_vsock_usr_hdr *)(poll_hdr + 1); + /* Skip headers page for data. */ + data_va = rx_va + PAGE_SIZE; + end_va = (unsigned char *)(rx_va + rx_map_len); + hdr_cnt = 0; + + while (data_va != end_va) { + int data_len = data_hdr->len; + + if (!data_hdr->len) { + if (fds.revents & (POLLHUP | POLLRDHUP) && + !hdr_cnt) + leave_loop = 1; + + break; + } + + while (data_len > 0) { + int i; + int to_read = (data_len < PAGE_SIZE) ? + data_len : PAGE_SIZE; + + for (i = 0; i < to_read; i++) + total_sum += data_va[i]; + + data_va += PAGE_SIZE; + data_len -= PAGE_SIZE; + } + + if (!stream) { + msg_bytes += data_hdr->len; + + if (data_hdr->flags) { + curr_hash += msg_bytes; + curr_hash = djb2(&curr_hash, + sizeof(curr_hash)); + msg_bytes = 0; + } + } + + data_hdr++; + hdr_cnt++; + } + + if (madvise((void *)rx_va, + rx_map_len, + MADV_DONTNEED)) { + perror("madvise"); + exit(EXIT_FAILURE); + } + + if (leave_loop) + break; + } + } + + curr_hash += total_sum; + curr_hash = djb2(&curr_hash, sizeof(curr_hash)); + + if (munmap(rx_va, rx_map_len)) { + perror("munmap"); + exit(EXIT_FAILURE); + } + + remote_hash = control_readulong(NULL); + + if (curr_hash != remote_hash) { + fprintf(stderr, "sum mismatch %lu != %lu\n", + curr_hash, remote_hash); + exit(EXIT_FAILURE); + } + + close(fd); +} + +void test_seqpacket_zerocopy_rx_client(const struct test_opts *opts) +{ + test_connectible_zerocopy_rx_client(opts, false); +} + +void test_stream_zerocopy_rx_client(const struct test_opts *opts) +{ + test_connectible_zerocopy_rx_client(opts, true); +} + +static void test_connectible_zerocopy_rx_server(const struct test_opts *opts, + bool stream) +{ + size_t max_buf_size = TX_BUF_SIZE; + unsigned long curr_hash; + long total_sum = 0; + int n = TX_SEND_LOOPS; + int fd; + + if (stream) + fd = vsock_stream_accept(VMADDR_CID_ANY, 1234, NULL); + else + fd = vsock_seqpacket_accept(VMADDR_CID_ANY, 1234, NULL); + + if (fd < 0) { + perror("accept"); + exit(EXIT_FAILURE); + } + + curr_hash = 0; + + while (n) { + unsigned char *data; + size_t send_size; + size_t buf_size; + int i; + + buf_size = 1 + (rand() % max_buf_size); + + data = malloc(buf_size); + + if (!data) { + perror("malloc"); + exit(EXIT_FAILURE); + } + + for (i = 0; i < buf_size; i++) { + data[i] = rand() & 0xff; + total_sum += data[i]; + } + + send_size = write(fd, data, buf_size); + + if (send_size != buf_size) { + perror("write"); + exit(EXIT_FAILURE); + } + + if (!stream) { + curr_hash += send_size; + curr_hash = djb2(&curr_hash, sizeof(curr_hash)); + } + + free(data); + n--; + } + + curr_hash += total_sum; + curr_hash = djb2(&curr_hash, sizeof(curr_hash)); + control_writeulong(curr_hash); + + close(fd); +} + +void test_stream_zerocopy_rx_server(const struct test_opts *opts) +{ + test_connectible_zerocopy_rx_server(opts, true); +} + +void test_seqpacket_zerocopy_rx_server(const struct test_opts *opts) +{ + test_connectible_zerocopy_rx_server(opts, false); +} + +void test_stream_zerocopy_rx_client_loop_poll(const struct test_opts *opts) +{ + unsigned long remote_sum; + unsigned long total_sum; + unsigned long zc_on = 1; + size_t rx_map_len; + u32 poll_value = 0; + void *rx_va; + int fd; + + fd = vsock_stream_connect_opt(opts->peer_cid, 1234, + SO_VM_SOCKETS_ZEROCOPY, + (void *)&zc_on, sizeof(zc_on)); + if (fd < 0) { + perror("connect"); + exit(EXIT_FAILURE); + } + + rx_map_len = PAGE_SIZE * RX_MAPPING_PAGES; + + rx_va = mmap(NULL, rx_map_len, PROT_READ, MAP_SHARED, fd, 0); + if (rx_va == MAP_FAILED) { + perror("mmap"); + exit(EXIT_FAILURE); + } + + total_sum = 0; + + while (1) { + volatile struct virtio_vsock_usr_hdr_pref *poll_hdr; + struct virtio_vsock_usr_hdr *data_hdr; + unsigned char *data_va; + unsigned char *end_va; + int leave_loop; + socklen_t len; + int hdr_cnt; + + poll_hdr = (struct virtio_vsock_usr_hdr_pref *)rx_va; + + if (poll_value != ~0) { + do { + poll_value = poll_hdr->poll_value; + } while (!poll_value); + } + + len = sizeof(rx_va); + + if (getsockopt(fd, AF_VSOCK, + SO_VM_SOCKETS_MAP_RX, + &rx_va, &len) < 0) { + perror("getsockopt"); + exit(EXIT_FAILURE); + } + + data_va = rx_va + PAGE_SIZE; + end_va = (unsigned char *)(rx_va + rx_map_len); + data_hdr = (struct virtio_vsock_usr_hdr *)(poll_hdr + 1); + hdr_cnt = 0; + leave_loop = 0; + + while (data_va != end_va) { + int data_len = data_hdr->len; + + if (!data_hdr->len) { + /* Zero length in first header and there will + * be no more data, leave processing loop. + */ + if (!hdr_cnt && (poll_value == ~0)) + leave_loop = 1; + + break; + } + + while (data_len > 0) { + int i; + int to_read = (data_len < PAGE_SIZE) ? + data_len : PAGE_SIZE; + + for (i = 0; i < to_read; i++) + total_sum += data_va[i]; + + data_va += PAGE_SIZE; + data_len -= PAGE_SIZE; + } + + data_hdr++; + hdr_cnt++; + } + + if (madvise((void *)rx_va + PAGE_SIZE, + rx_map_len - PAGE_SIZE, + MADV_DONTNEED)) { + perror("madvise"); + exit(EXIT_FAILURE); + } + + if (leave_loop) + break; + } + + if (munmap(rx_va, rx_map_len)) { + perror("munmap"); + exit(EXIT_FAILURE); + } + + remote_sum = control_readulong(NULL); + + if (total_sum != remote_sum) { + fprintf(stderr, "loop sum mismatch %lu != %lu\n", + total_sum, remote_sum); + exit(EXIT_FAILURE); + } + + close(fd); +} + +void test_stream_zerocopy_rx_server_loop_poll(const struct test_opts *opts) +{ + size_t max_buf_size = TX_BUF_SIZE; + unsigned long total_sum; + int n = TX_SEND_LOOPS; + int fd; + + fd = vsock_stream_accept(VMADDR_CID_ANY, 1234, NULL); + if (fd < 0) { + perror("accept"); + exit(EXIT_FAILURE); + } + + total_sum = 0; + + while (n) { + unsigned char *data; + size_t buf_size; + int i; + + buf_size = 1 + (rand() % max_buf_size); + + data = malloc(buf_size); + + if (!data) { + perror("malloc"); + exit(EXIT_FAILURE); + } + + for (i = 0; i < buf_size; i++) { + data[i] = rand() & 0xff; + total_sum += data[i]; + } + + if (write(fd, data, buf_size) != buf_size) { + perror("write"); + exit(EXIT_FAILURE); + } + + free(data); + n--; + } + + control_writeulong(total_sum); + + close(fd); +} + +void test_stream_zerocopy_rx_inv_client(const struct test_opts *opts) +{ + size_t map_size = PAGE_SIZE * 5; + unsigned long zc_on = 1; + socklen_t len; + void *map_va; + int fd; + + /* Try zerocopy with disable option. */ + fd = vsock_stream_connect_opt(opts->peer_cid, 1234, SO_VM_SOCKETS_ZEROCOPY, + (void *)&zc_on, sizeof(zc_on)); + if (fd < 0) { + perror("connect"); + exit(EXIT_FAILURE); + } + + len = sizeof(map_va); + map_va = 0; + + /* Try zerocopy with invalid mapping address. */ + if (getsockopt(fd, AF_VSOCK, SO_VM_SOCKETS_MAP_RX, + &map_va, &len) == 0) { + perror("getsockopt"); + exit(EXIT_FAILURE); + } + + /* Try zerocopy with valid, but not socket mapping. */ + map_va = mmap(NULL, map_size, PROT_READ, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + if (map_va == MAP_FAILED) { + perror("anon mmap"); + exit(EXIT_FAILURE); + } + + if (getsockopt(fd, AF_VSOCK, SO_VM_SOCKETS_MAP_RX, + &map_va, &len) == 0) { + perror("getsockopt"); + exit(EXIT_FAILURE); + } + + if (munmap(map_va, map_size)) { + perror("munmap"); + exit(EXIT_FAILURE); + } + + /* Try zerocopy with valid, but too small mapping. */ + map_va = mmap(NULL, PAGE_SIZE, PROT_READ, MAP_SHARED, fd, 0); + if (map_va != MAP_FAILED) { + perror("socket mmap small"); + exit(EXIT_FAILURE); + } + + if (getsockopt(fd, AF_VSOCK, SO_VM_SOCKETS_MAP_RX, + &map_va, &len) == 0) { + perror("getsockopt"); + exit(EXIT_FAILURE); + } + + /* Try zerocopy with valid mapping, but not from first byte. */ + map_va = mmap(NULL, map_size, PROT_READ, MAP_SHARED, fd, 0); + if (map_va == MAP_FAILED) { + perror("socket mmap"); + exit(EXIT_FAILURE); + } + + map_va += PAGE_SIZE; + + if (getsockopt(fd, AF_VSOCK, SO_VM_SOCKETS_MAP_RX, + &map_va, &len) == 0) { + perror("getsockopt"); + exit(EXIT_FAILURE); + } + + if (munmap(map_va - PAGE_SIZE, map_size)) { + perror("munmap"); + exit(EXIT_FAILURE); + } + + control_writeln("DONE"); + + close(fd); +} + +void test_stream_zerocopy_rx_inv_server(const struct test_opts *opts) +{ + int fd; + + fd = vsock_stream_accept(VMADDR_CID_ANY, 1234, NULL); + + if (fd < 0) { + perror("accept"); + exit(EXIT_FAILURE); + } + + control_expectln("DONE"); + + close(fd); +} + diff --git a/tools/testing/vsock/vsock_test_zerocopy.h b/tools/testing/vsock/vsock_test_zerocopy.h new file mode 100644 index 000000000000..a818bd65b376 --- /dev/null +++ b/tools/testing/vsock/vsock_test_zerocopy.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef VSOCK_TEST_ZEROCOPY_H +#define VSOCK_TEST_ZEROCOPY_H + +void test_stream_zerocopy_rx_client(const struct test_opts *opts); +void test_stream_zerocopy_rx_server(const struct test_opts *opts); +void test_seqpacket_zerocopy_rx_client(const struct test_opts *opts); +void test_seqpacket_zerocopy_rx_server(const struct test_opts *opts); +void test_stream_zerocopy_rx_client_loop_poll(const struct test_opts *opts); +void test_stream_zerocopy_rx_server_loop_poll(const struct test_opts *opts); +void test_stream_zerocopy_rx_inv_client(const struct test_opts *opts); +void test_stream_zerocopy_rx_inv_server(const struct test_opts *opts); + +#endif /* VSOCK_TEST_ZEROCOPY_H */ -- 2.35.0