[RFC PATCH v2 7/8] test/vsock: add receive zerocopy tests
From: Arseniy Krasnov
Date: Fri Jun 03 2022 - 01:43:55 EST
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 | 11 +
tools/include/uapi/linux/vm_sockets.h | 8 +
tools/testing/vsock/control.c | 34 +++
tools/testing/vsock/control.h | 2 +
tools/testing/vsock/vsock_test.c | 295 ++++++++++++++++++++++++
5 files changed, 350 insertions(+)
create mode 100644 tools/include/uapi/linux/virtio_vsock.h
create mode 100644 tools/include/uapi/linux/vm_sockets.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..c23d85e73d04
--- /dev/null
+++ b/tools/include/uapi/linux/virtio_vsock.h
@@ -0,0 +1,11 @@
+/* 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;
+ u32 copy_len;
+} __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/control.c b/tools/testing/vsock/control.c
index 4874872fc5a3..00a654e8f137 100644
--- a/tools/testing/vsock/control.c
+++ b/tools/testing/vsock/control.c
@@ -141,6 +141,40 @@ void control_writeln(const char *str)
timeout_end();
}
+void control_writelong(long value)
+{
+ char str[32];
+
+ if (snprintf(str, sizeof(str), "%li", value) >= sizeof(str)) {
+ perror("snprintf");
+ exit(EXIT_FAILURE);
+ }
+
+ control_writeln(str);
+}
+
+long control_readlong(bool *ok)
+{
+ long value = -1;
+ char *str;
+
+ if (ok)
+ *ok = false;
+
+ str = control_readln();
+
+ if (str == NULL)
+ return value;
+
+ value = strtol(str, NULL, 10);
+ free(str);
+
+ if (ok)
+ *ok = true;
+
+ return value;
+}
+
/* Return the next line from the control socket (without the trailing newline).
*
* The program terminates if a timeout occurs.
diff --git a/tools/testing/vsock/control.h b/tools/testing/vsock/control.h
index 51814b4f9ac1..5272ad20e850 100644
--- a/tools/testing/vsock/control.h
+++ b/tools/testing/vsock/control.h
@@ -9,7 +9,9 @@ void control_init(const char *control_host, const char *control_port,
void control_cleanup(void);
void control_writeln(const char *str);
char *control_readln(void);
+long control_readlong(bool *ok);
void control_expectln(const char *str);
bool control_cmpln(char *line, const char *str, bool fail);
+void control_writelong(long value);
#endif /* CONTROL_H */
diff --git a/tools/testing/vsock/vsock_test.c b/tools/testing/vsock/vsock_test.c
index dc577461afc2..1b8c40bab33e 100644
--- a/tools/testing/vsock/vsock_test.c
+++ b/tools/testing/vsock/vsock_test.c
@@ -18,11 +18,16 @@
#include <sys/socket.h>
#include <time.h>
#include <sys/mman.h>
+#include <poll.h>
+#include <uapi/linux/virtio_vsock.h>
+#include <uapi/linux/vm_sockets.h>
#include "timeout.h"
#include "control.h"
#include "util.h"
+#define PAGE_SIZE 4096
+
static void test_stream_connection_reset(const struct test_opts *opts)
{
union {
@@ -596,6 +601,285 @@ static void test_seqpacket_invalid_rec_buffer_server(const struct test_opts *opt
close(fd);
}
+static void test_stream_zerocopy_rx_client(const struct test_opts *opts)
+{
+ unsigned long total_sum;
+ unsigned long zc_on = 1;
+ size_t rx_map_len;
+ long rec_value;
+ void *rx_va;
+ int fd;
+
+ fd = vsock_stream_connect(opts->peer_cid, 1234);
+ if (fd < 0) {
+ perror("connect");
+ exit(EXIT_FAILURE);
+ }
+
+ if (setsockopt(fd, AF_VSOCK, SO_VM_SOCKETS_ZEROCOPY,
+ (void *)&zc_on, sizeof(zc_on))) {
+ perror("setsockopt");
+ exit(EXIT_FAILURE);
+ }
+
+ rx_map_len = PAGE_SIZE * 3;
+
+ 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) {
+ struct pollfd fds = { 0 };
+ int hungup = 0;
+ 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);
+ }
+
+ if (fds.revents & POLLIN) {
+ struct virtio_vsock_usr_hdr *hdr;
+ uintptr_t tmp_rx_va = (uintptr_t)rx_va;
+ unsigned char *data_va;
+ unsigned char *end_va;
+ socklen_t len = sizeof(tmp_rx_va);
+
+ if (getsockopt(fd, AF_VSOCK,
+ SO_VM_SOCKETS_MAP_RX,
+ &tmp_rx_va, &len) < 0) {
+ perror("getsockopt");
+ exit(EXIT_FAILURE);
+ }
+
+ hdr = (struct virtio_vsock_usr_hdr *)rx_va;
+ /* Skip headers page for data. */
+ data_va = rx_va + PAGE_SIZE;
+ end_va = (unsigned char *)(tmp_rx_va + rx_map_len);
+
+ while (data_va != end_va) {
+ int data_len = hdr->len;
+
+ if (!hdr->len) {
+ if (fds.revents & (POLLHUP | POLLRDHUP)) {
+ if (hdr == rx_va)
+ hungup = 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;
+ }
+
+ hdr++;
+ }
+
+ if (madvise((void *)rx_va, rx_map_len,
+ MADV_DONTNEED)) {
+ perror("madvise");
+ exit(EXIT_FAILURE);
+ }
+
+ if (hungup)
+ break;
+ }
+ }
+
+ if (munmap(rx_va, rx_map_len)) {
+ perror("munmap");
+ exit(EXIT_FAILURE);
+ }
+
+ rec_value = control_readlong(NULL);
+
+ if (total_sum != rec_value) {
+ fprintf(stderr, "sum mismatch %lu != %lu\n",
+ total_sum, rec_value);
+ exit(EXIT_FAILURE);
+ }
+
+ close(fd);
+}
+
+static void test_stream_zerocopy_rx_server(const struct test_opts *opts)
+{
+ size_t max_buf_size = 40000;
+ long total_sum = 0;
+ int n = 10;
+ int fd;
+
+ fd = vsock_stream_accept(VMADDR_CID_ANY, 1234, NULL);
+ if (fd < 0) {
+ perror("accept");
+ exit(EXIT_FAILURE);
+ }
+
+ 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_writelong(total_sum);
+
+ close(fd);
+}
+
+static 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;
+
+ fd = vsock_stream_connect(opts->peer_cid, 1234);
+ if (fd < 0) {
+ perror("connect");
+ exit(EXIT_FAILURE);
+ }
+
+ len = sizeof(map_va);
+ map_va = 0;
+
+ if (setsockopt(fd, AF_VSOCK, SO_VM_SOCKETS_ZEROCOPY,
+ (void *)&zc_on, sizeof(zc_on))) {
+ perror("setsockopt");
+ exit(EXIT_FAILURE);
+ }
+
+ /* 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");
+ 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, PAGE_SIZE)) {
+ perror("munmap");
+ 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);
+}
+
+static 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);
+}
+
static struct test_case test_cases[] = {
{
.name = "SOCK_STREAM connection reset",
@@ -646,6 +930,16 @@ static struct test_case test_cases[] = {
.run_client = test_seqpacket_invalid_rec_buffer_client,
.run_server = test_seqpacket_invalid_rec_buffer_server,
},
+ {
+ .name = "SOCK_STREAM zerocopy receive",
+ .run_client = test_stream_zerocopy_rx_client,
+ .run_server = test_stream_zerocopy_rx_server,
+ },
+ {
+ .name = "SOCK_STREAM zerocopy invalid",
+ .run_client = test_stream_zerocopy_rx_inv_client,
+ .run_server = test_stream_zerocopy_rx_inv_server,
+ },
{},
};
@@ -729,6 +1023,7 @@ int main(int argc, char **argv)
.peer_cid = VMADDR_CID_ANY,
};
+ srand(time(NULL));
init_signals();
for (;;) {
--
2.25.1