new file mode 100644
@@ -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 */
new file mode 100644
@@ -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 */
@@ -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
@@ -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
@@ -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,
@@ -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,
+ },
{},
};
new file mode 100644
@@ -0,0 +1,530 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ *
+ *
+ * Copyright (C) 2022 Sberdevices, Inc.
+ *
+ * Author: Arseniy Krasnov <AVKrasnov@sberdevices.ru>
+ */
+
+#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);
+}
+
new file mode 100644
@@ -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 */
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@sberdevices.ru> --- 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