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@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);
+}
+
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 */
