Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
*.d
vsock_test
vsock_diag_test
vsock_perf
vsock_uring_test
+29
View File
@@ -0,0 +1,29 @@
# SPDX-License-Identifier: GPL-2.0-only
all: test vsock_perf
test: vsock_test vsock_diag_test vsock_uring_test
vsock_test: vsock_test.o vsock_test_zerocopy.o timeout.o control.o util.o msg_zerocopy_common.o
vsock_diag_test: vsock_diag_test.o timeout.o control.o util.o
vsock_perf: vsock_perf.o msg_zerocopy_common.o
vsock_test: LDLIBS = -lpthread
vsock_uring_test: LDLIBS = -luring
vsock_uring_test: control.o util.o vsock_uring_test.o timeout.o msg_zerocopy_common.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
.PHONY: all test clean
clean:
${RM} *.o *.d vsock_test vsock_diag_test vsock_perf vsock_uring_test
-include *.d
VSOCK_INSTALL_PATH ?=
install: all
ifdef VSOCK_INSTALL_PATH
mkdir -p $(VSOCK_INSTALL_PATH)
install -m 744 vsock_test $(VSOCK_INSTALL_PATH)
install -m 744 vsock_perf $(VSOCK_INSTALL_PATH)
install -m 744 vsock_diag_test $(VSOCK_INSTALL_PATH)
install -m 744 vsock_uring_test $(VSOCK_INSTALL_PATH)
else
$(error Error: set VSOCK_INSTALL_PATH to use install)
endif
+86
View File
@@ -0,0 +1,86 @@
AF_VSOCK test suite
-------------------
These tests exercise net/vmw_vsock/ host<->guest sockets for VMware, KVM, and
Hyper-V.
The following tests are available:
* vsock_test - core AF_VSOCK socket functionality
* vsock_diag_test - vsock_diag.ko module for listing open sockets
The following prerequisite steps are not automated and must be performed prior
to running tests:
1. Build the kernel, make headers_install, and build these tests.
2. Install the kernel and tests on the host.
3. Install the kernel and tests inside the guest.
4. Boot the guest and ensure that the AF_VSOCK transport is enabled.
Invoke test binaries in both directions as follows:
# host=server, guest=client
(host)# $TEST_BINARY --mode=server \
--control-port=1234 \
--peer-cid=3
(guest)# $TEST_BINARY --mode=client \
--control-host=$HOST_IP \
--control-port=1234 \
--peer-cid=2
# host=client, guest=server
(guest)# $TEST_BINARY --mode=server \
--control-port=1234 \
--peer-cid=2
(host)# $TEST_BINARY --mode=client \
--control-port=$GUEST_IP \
--control-port=1234 \
--peer-cid=3
Some tests are designed to produce kernel memory leaks. Leaks detection,
however, is deferred to Kernel Memory Leak Detector. It is recommended to enable
kmemleak (CONFIG_DEBUG_KMEMLEAK=y) and explicitly trigger a scan after each test
suite run, e.g.
# echo clear > /sys/kernel/debug/kmemleak
# $TEST_BINARY ...
# echo "wait for any grace periods" && sleep 2
# echo scan > /sys/kernel/debug/kmemleak
# echo "wait for kmemleak" && sleep 5
# echo scan > /sys/kernel/debug/kmemleak
# cat /sys/kernel/debug/kmemleak
For more information see Documentation/dev-tools/kmemleak.rst.
vsock_perf utility
-------------------
'vsock_perf' is a simple tool to measure vsock performance. It works in
sender/receiver modes: sender connect to peer at the specified port and
starts data transmission to the receiver. After data processing is done,
it prints several metrics(see below).
Usage:
# run as sender
# connect to CID 2, port 1234, send 1G of data, tx buf size is 1M
./vsock_perf --sender 2 --port 1234 --bytes 1G --buf-size 1M
Output:
tx performance: A Gbits/s
Output explanation:
A is calculated as "number of bits to send" / "time in tx loop"
# run as receiver
# listen port 1234, rx buf size is 1M, socket buf size is 1G, SO_RCVLOWAT is 64K
./vsock_perf --port 1234 --buf-size 1M --vsk-size 1G --rcvlowat 64K
Output:
rx performance: A Gbits/s
total in 'read()': B sec
POLLIN wakeups: C
average in 'read()': D ns
Output explanation:
A is calculated as "number of received bits" / "time in rx loop".
B is time, spent in 'read()' system call(excluding 'poll()')
C is number of 'poll()' wake ups with POLLIN bit set.
D is B / C, e.g. average amount of time, spent in single 'read()'.
+251
View File
@@ -0,0 +1,251 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Control socket for client/server test execution
*
* Copyright (C) 2017 Red Hat, Inc.
*
* Author: Stefan Hajnoczi <stefanha@redhat.com>
*/
/* The client and server may need to coordinate to avoid race conditions like
* the client attempting to connect to a socket that the server is not
* listening on yet. The control socket offers a communications channel for
* such coordination tasks.
*
* If the client calls control_expectln("LISTENING"), then it will block until
* the server calls control_writeln("LISTENING"). This provides a simple
* mechanism for coordinating between the client and the server.
*/
#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "timeout.h"
#include "control.h"
#include "util.h"
static int control_fd = -1;
/* Open the control socket, either in server or client mode */
void control_init(const char *control_host,
const char *control_port,
bool server)
{
struct addrinfo hints = {
.ai_socktype = SOCK_STREAM,
};
struct addrinfo *result = NULL;
struct addrinfo *ai;
int ret;
ret = getaddrinfo(control_host, control_port, &hints, &result);
if (ret != 0) {
fprintf(stderr, "%s\n", gai_strerror(ret));
exit(EXIT_FAILURE);
}
for (ai = result; ai; ai = ai->ai_next) {
int fd;
fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (fd < 0)
continue;
if (!server) {
if (connect(fd, ai->ai_addr, ai->ai_addrlen) < 0)
goto next;
control_fd = fd;
printf("Control socket connected to %s:%s.\n",
control_host, control_port);
break;
}
setsockopt_int_check(fd, SOL_SOCKET, SO_REUSEADDR, 1,
"setsockopt SO_REUSEADDR");
if (bind(fd, ai->ai_addr, ai->ai_addrlen) < 0)
goto next;
if (listen(fd, 1) < 0)
goto next;
printf("Control socket listening on %s:%s\n",
control_host, control_port);
fflush(stdout);
control_fd = accept(fd, NULL, 0);
close(fd);
if (control_fd < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
printf("Control socket connection accepted...\n");
break;
next:
close(fd);
}
if (control_fd < 0) {
fprintf(stderr, "Control socket initialization failed. Invalid address %s:%s?\n",
control_host, control_port);
exit(EXIT_FAILURE);
}
freeaddrinfo(result);
}
/* Free resources */
void control_cleanup(void)
{
close(control_fd);
control_fd = -1;
}
/* Write a line to the control socket */
void control_writeln(const char *str)
{
ssize_t len = strlen(str);
ssize_t ret;
timeout_begin(TIMEOUT);
do {
ret = send(control_fd, str, len, MSG_MORE);
timeout_check("send");
} while (ret < 0 && errno == EINTR);
if (ret != len) {
perror("send");
exit(EXIT_FAILURE);
}
do {
ret = send(control_fd, "\n", 1, 0);
timeout_check("send");
} while (ret < 0 && errno == EINTR);
if (ret != 1) {
perror("send");
exit(EXIT_FAILURE);
}
timeout_end();
}
void control_writeulong(unsigned long value)
{
char str[32];
if (snprintf(str, sizeof(str), "%lu", value) >= sizeof(str)) {
perror("snprintf");
exit(EXIT_FAILURE);
}
control_writeln(str);
}
unsigned long control_readulong(void)
{
unsigned long value;
char *str;
str = control_readln();
if (!str)
exit(EXIT_FAILURE);
value = strtoul(str, NULL, 10);
free(str);
return value;
}
/* Return the next line from the control socket (without the trailing newline).
*
* The program terminates if a timeout occurs.
*
* The caller must free() the returned string.
*/
char *control_readln(void)
{
char *buf = NULL;
size_t idx = 0;
size_t buflen = 0;
timeout_begin(TIMEOUT);
for (;;) {
ssize_t ret;
if (idx >= buflen) {
char *new_buf;
new_buf = realloc(buf, buflen + 80);
if (!new_buf) {
perror("realloc");
exit(EXIT_FAILURE);
}
buf = new_buf;
buflen += 80;
}
do {
ret = recv(control_fd, &buf[idx], 1, 0);
timeout_check("recv");
} while (ret < 0 && errno == EINTR);
if (ret == 0) {
fprintf(stderr, "unexpected EOF on control socket\n");
exit(EXIT_FAILURE);
}
if (ret != 1) {
perror("recv");
exit(EXIT_FAILURE);
}
if (buf[idx] == '\n') {
buf[idx] = '\0';
break;
}
idx++;
}
timeout_end();
return buf;
}
/* Wait until a given line is received or a timeout occurs */
void control_expectln(const char *str)
{
char *line;
line = control_readln();
control_cmpln(line, str, true);
free(line);
}
bool control_cmpln(char *line, const char *str, bool fail)
{
if (strcmp(str, line) == 0)
return true;
if (fail) {
fprintf(stderr, "expected \"%s\" on control socket, got \"%s\"\n",
str, line);
exit(EXIT_FAILURE);
}
return false;
}
+17
View File
@@ -0,0 +1,17 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef CONTROL_H
#define CONTROL_H
#include <stdbool.h>
void control_init(const char *control_host, const char *control_port,
bool server);
void control_cleanup(void);
void control_writeln(const char *str);
char *control_readln(void);
unsigned long control_readulong(void);
void control_expectln(const char *str);
bool control_cmpln(char *line, const char *str, bool fail);
void control_writeulong(unsigned long value);
#endif /* CONTROL_H */
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Some common code for MSG_ZEROCOPY logic
*
* Copyright (C) 2023 SberDevices.
*
* Author: Arseniy Krasnov <avkrasnov@salutedevices.com>
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/errqueue.h>
#include "msg_zerocopy_common.h"
void vsock_recv_completion(int fd, const bool *zerocopied)
{
struct sock_extended_err *serr;
struct msghdr msg = { 0 };
char cmsg_data[128];
struct cmsghdr *cm;
ssize_t res;
msg.msg_control = cmsg_data;
msg.msg_controllen = sizeof(cmsg_data);
res = recvmsg(fd, &msg, MSG_ERRQUEUE);
if (res) {
fprintf(stderr, "failed to read error queue: %zi\n", res);
exit(EXIT_FAILURE);
}
cm = CMSG_FIRSTHDR(&msg);
if (!cm) {
fprintf(stderr, "cmsg: no cmsg\n");
exit(EXIT_FAILURE);
}
if (cm->cmsg_level != SOL_VSOCK) {
fprintf(stderr, "cmsg: unexpected 'cmsg_level'\n");
exit(EXIT_FAILURE);
}
if (cm->cmsg_type != VSOCK_RECVERR) {
fprintf(stderr, "cmsg: unexpected 'cmsg_type'\n");
exit(EXIT_FAILURE);
}
serr = (void *)CMSG_DATA(cm);
if (serr->ee_origin != SO_EE_ORIGIN_ZEROCOPY) {
fprintf(stderr, "serr: wrong origin: %u\n", serr->ee_origin);
exit(EXIT_FAILURE);
}
if (serr->ee_errno) {
fprintf(stderr, "serr: wrong error code: %u\n", serr->ee_errno);
exit(EXIT_FAILURE);
}
/* This flag is used for tests, to check that transmission was
* performed as expected: zerocopy or fallback to copy. If NULL
* - don't care.
*/
if (!zerocopied)
return;
if (*zerocopied && (serr->ee_code & SO_EE_CODE_ZEROCOPY_COPIED)) {
fprintf(stderr, "serr: was copy instead of zerocopy\n");
exit(EXIT_FAILURE);
}
if (!*zerocopied && !(serr->ee_code & SO_EE_CODE_ZEROCOPY_COPIED)) {
fprintf(stderr, "serr: was zerocopy instead of copy\n");
exit(EXIT_FAILURE);
}
}
@@ -0,0 +1,17 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef MSG_ZEROCOPY_COMMON_H
#define MSG_ZEROCOPY_COMMON_H
#include <stdbool.h>
#ifndef SOL_VSOCK
#define SOL_VSOCK 287
#endif
#ifndef VSOCK_RECVERR
#define VSOCK_RECVERR 1
#endif
void vsock_recv_completion(int fd, const bool *zerocopied);
#endif /* MSG_ZEROCOPY_COMMON_H */
+78
View File
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Timeout API for single-threaded programs that use blocking
* syscalls (read/write/send/recv/connect/accept).
*
* Copyright (C) 2017 Red Hat, Inc.
*
* Author: Stefan Hajnoczi <stefanha@redhat.com>
*/
/* Use the following pattern:
*
* timeout_begin(TIMEOUT);
* do {
* ret = accept(...);
* timeout_check("accept");
* } while (ret < 0 && ret == EINTR);
* timeout_end();
*/
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdio.h>
#include <time.h>
#include "timeout.h"
static volatile bool timeout;
/* SIGALRM handler function. Do not use sleep(2), alarm(2), or
* setitimer(2) while using this API - they may interfere with each
* other.
*
* If you need to sleep, please use timeout_sleep() provided by this API.
*/
void sigalrm(int signo)
{
timeout = true;
}
/* Start a timeout. Call timeout_check() to verify that the timeout hasn't
* expired. timeout_end() must be called to stop the timeout. Timeouts cannot
* be nested.
*/
void timeout_begin(unsigned int seconds)
{
alarm(seconds);
}
/* Exit with an error message if the timeout has expired */
void timeout_check(const char *operation)
{
if (timeout) {
fprintf(stderr, "%s timed out\n", operation);
exit(EXIT_FAILURE);
}
}
/* Stop a timeout */
void timeout_end(void)
{
alarm(0);
timeout = false;
}
/* Sleep in a timeout section.
*
* nanosleep(2) can be used with this API since POSIX.1 explicitly
* specifies that it does not interact with signals.
*/
int timeout_usleep(useconds_t usec)
{
struct timespec ts = {
.tv_sec = usec / 1000000,
.tv_nsec = (usec % 1000000) * 1000,
};
return nanosleep(&ts, NULL);
}
+16
View File
@@ -0,0 +1,16 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef TIMEOUT_H
#define TIMEOUT_H
enum {
/* Default timeout */
TIMEOUT = 10 /* seconds */
};
void sigalrm(int signo);
void timeout_begin(unsigned int seconds);
void timeout_check(const char *operation);
void timeout_end(void);
int timeout_usleep(useconds_t usec);
#endif /* TIMEOUT_H */
+954
View File
@@ -0,0 +1,954 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* vsock test utilities
*
* Copyright (C) 2017 Red Hat, Inc.
*
* Author: Stefan Hajnoczi <stefanha@redhat.com>
*/
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <assert.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/sockios.h>
#include "timeout.h"
#include "control.h"
#include "util.h"
#define KALLSYMS_PATH "/proc/kallsyms"
#define KALLSYMS_LINE_LEN 512
/* Install signal handlers */
void init_signals(void)
{
struct sigaction act = {
.sa_handler = sigalrm,
};
sigaction(SIGALRM, &act, NULL);
signal(SIGPIPE, SIG_IGN);
}
static unsigned int parse_uint(const char *str, const char *err_str)
{
char *endptr = NULL;
unsigned long n;
errno = 0;
n = strtoul(str, &endptr, 10);
if (errno || *endptr != '\0') {
fprintf(stderr, "malformed %s \"%s\"\n", err_str, str);
exit(EXIT_FAILURE);
}
return n;
}
/* Parse a CID in string representation */
unsigned int parse_cid(const char *str)
{
return parse_uint(str, "CID");
}
/* Parse a port in string representation */
unsigned int parse_port(const char *str)
{
return parse_uint(str, "port");
}
/* Wait for the remote to close the connection */
void vsock_wait_remote_close(int fd)
{
struct epoll_event ev;
int epollfd, nfds;
epollfd = epoll_create1(0);
if (epollfd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
ev.events = EPOLLRDHUP | EPOLLHUP;
ev.data.fd = fd;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) {
perror("epoll_ctl");
exit(EXIT_FAILURE);
}
nfds = epoll_wait(epollfd, &ev, 1, TIMEOUT * 1000);
if (nfds == -1) {
perror("epoll_wait");
exit(EXIT_FAILURE);
}
if (nfds == 0) {
fprintf(stderr, "epoll_wait timed out\n");
exit(EXIT_FAILURE);
}
assert(nfds == 1);
assert(ev.events & (EPOLLRDHUP | EPOLLHUP));
assert(ev.data.fd == fd);
close(epollfd);
}
/* Wait until ioctl gives an expected int value.
* Return false if the op is not supported.
*/
bool vsock_ioctl_int(int fd, unsigned long op, int expected)
{
int actual, ret;
char name[32];
snprintf(name, sizeof(name), "ioctl(%lu)", op);
timeout_begin(TIMEOUT);
do {
ret = ioctl(fd, op, &actual);
if (ret < 0) {
if (errno == EOPNOTSUPP || errno == ENOTTY)
break;
perror(name);
exit(EXIT_FAILURE);
}
timeout_check(name);
} while (actual != expected);
timeout_end();
return ret >= 0;
}
/* Wait until transport reports no data left to be sent.
* Return false if transport does not implement the unsent_bytes() callback.
*/
bool vsock_wait_sent(int fd)
{
return vsock_ioctl_int(fd, SIOCOUTQ, 0);
}
/* Create socket <type>, bind to <cid, port>.
* Return the file descriptor, or -1 on error.
*/
int vsock_bind_try(unsigned int cid, unsigned int port, int type)
{
struct sockaddr_vm sa = {
.svm_family = AF_VSOCK,
.svm_cid = cid,
.svm_port = port,
};
int fd, saved_errno;
fd = socket(AF_VSOCK, type, 0);
if (fd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
if (bind(fd, (struct sockaddr *)&sa, sizeof(sa))) {
saved_errno = errno;
close(fd);
errno = saved_errno;
fd = -1;
}
return fd;
}
/* Create socket <type>, bind to <cid, port> and return the file descriptor. */
int vsock_bind(unsigned int cid, unsigned int port, int type)
{
int fd;
fd = vsock_bind_try(cid, port, type);
if (fd < 0) {
perror("bind");
exit(EXIT_FAILURE);
}
return fd;
}
int vsock_connect_fd(int fd, unsigned int cid, unsigned int port)
{
struct sockaddr_vm sa = {
.svm_family = AF_VSOCK,
.svm_cid = cid,
.svm_port = port,
};
int ret;
timeout_begin(TIMEOUT);
do {
ret = connect(fd, (struct sockaddr *)&sa, sizeof(sa));
timeout_check("connect");
} while (ret < 0 && errno == EINTR);
timeout_end();
return ret;
}
/* Bind to <bind_port>, connect to <cid, port> and return the file descriptor. */
int vsock_bind_connect(unsigned int cid, unsigned int port, unsigned int bind_port, int type)
{
int client_fd;
client_fd = vsock_bind(VMADDR_CID_ANY, bind_port, type);
if (vsock_connect_fd(client_fd, cid, port)) {
perror("connect");
exit(EXIT_FAILURE);
}
return client_fd;
}
/* Connect to <cid, port> and return the file descriptor. */
int vsock_connect(unsigned int cid, unsigned int port, int type)
{
int fd;
control_expectln("LISTENING");
fd = socket(AF_VSOCK, type, 0);
if (fd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
if (vsock_connect_fd(fd, cid, port)) {
int old_errno = errno;
close(fd);
fd = -1;
errno = old_errno;
}
return fd;
}
int vsock_stream_connect(unsigned int cid, unsigned int port)
{
return vsock_connect(cid, port, SOCK_STREAM);
}
int vsock_seqpacket_connect(unsigned int cid, unsigned int port)
{
return vsock_connect(cid, port, SOCK_SEQPACKET);
}
/* Listen on <cid, port> and return the file descriptor. */
static int vsock_listen(unsigned int cid, unsigned int port, int type)
{
int fd;
fd = vsock_bind(cid, port, type);
if (listen(fd, 1) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
return fd;
}
/* Listen on <cid, port> and return the first incoming connection. The remote
* address is stored to clientaddrp. clientaddrp may be NULL.
*/
int vsock_accept(unsigned int cid, unsigned int port,
struct sockaddr_vm *clientaddrp, int type)
{
union {
struct sockaddr sa;
struct sockaddr_vm svm;
} clientaddr;
socklen_t clientaddr_len = sizeof(clientaddr.svm);
int fd, client_fd, old_errno;
fd = vsock_listen(cid, port, type);
control_writeln("LISTENING");
timeout_begin(TIMEOUT);
do {
client_fd = accept(fd, &clientaddr.sa, &clientaddr_len);
timeout_check("accept");
} while (client_fd < 0 && errno == EINTR);
timeout_end();
old_errno = errno;
close(fd);
errno = old_errno;
if (client_fd < 0)
return client_fd;
if (clientaddr_len != sizeof(clientaddr.svm)) {
fprintf(stderr, "unexpected addrlen from accept(2), %zu\n",
(size_t)clientaddr_len);
exit(EXIT_FAILURE);
}
if (clientaddr.sa.sa_family != AF_VSOCK) {
fprintf(stderr, "expected AF_VSOCK from accept(2), got %d\n",
clientaddr.sa.sa_family);
exit(EXIT_FAILURE);
}
if (clientaddrp)
*clientaddrp = clientaddr.svm;
return client_fd;
}
int vsock_stream_accept(unsigned int cid, unsigned int port,
struct sockaddr_vm *clientaddrp)
{
return vsock_accept(cid, port, clientaddrp, SOCK_STREAM);
}
int vsock_stream_listen(unsigned int cid, unsigned int port)
{
return vsock_listen(cid, port, SOCK_STREAM);
}
int vsock_seqpacket_accept(unsigned int cid, unsigned int port,
struct sockaddr_vm *clientaddrp)
{
return vsock_accept(cid, port, clientaddrp, SOCK_SEQPACKET);
}
/* Transmit bytes from a buffer and check the return value.
*
* expected_ret:
* <0 Negative errno (for testing errors)
* 0 End-of-file
* >0 Success (bytes successfully written)
*/
void send_buf(int fd, const void *buf, size_t len, int flags,
ssize_t expected_ret)
{
ssize_t nwritten = 0;
ssize_t ret;
timeout_begin(TIMEOUT);
do {
ret = send(fd, buf + nwritten, len - nwritten, flags);
timeout_check("send");
if (ret < 0 && errno == EINTR)
continue;
if (ret <= 0)
break;
nwritten += ret;
} while (nwritten < len);
timeout_end();
if (expected_ret < 0) {
if (ret != -1) {
fprintf(stderr, "bogus send(2) return value %zd (expected %zd)\n",
ret, expected_ret);
exit(EXIT_FAILURE);
}
if (errno != -expected_ret) {
perror("send");
exit(EXIT_FAILURE);
}
return;
}
if (ret < 0) {
perror("send");
exit(EXIT_FAILURE);
}
if (nwritten != expected_ret) {
if (ret == 0)
fprintf(stderr, "unexpected EOF while sending bytes\n");
fprintf(stderr, "bogus send(2) bytes written %zd (expected %zd)\n",
nwritten, expected_ret);
exit(EXIT_FAILURE);
}
}
#define RECV_PEEK_RETRY_USEC (10 * 1000)
/* Receive bytes in a buffer and check the return value.
*
* When MSG_PEEK is set, recv() is retried until it returns at least
* expected_ret bytes. The function returns on error, EOF, or timeout
* as usual.
*
* expected_ret:
* <0 Negative errno (for testing errors)
* 0 End-of-file
* >0 Success (bytes successfully read)
*/
void recv_buf(int fd, void *buf, size_t len, int flags, ssize_t expected_ret)
{
ssize_t nread = 0;
ssize_t ret;
timeout_begin(TIMEOUT);
do {
ret = recv(fd, buf + nread, len - nread, flags);
timeout_check("recv");
if (ret < 0 && errno == EINTR)
continue;
if (ret <= 0)
break;
if (flags & MSG_PEEK) {
if (ret >= expected_ret) {
nread = ret;
break;
}
timeout_usleep(RECV_PEEK_RETRY_USEC);
continue;
}
nread += ret;
} while (nread < len);
timeout_end();
if (expected_ret < 0) {
if (ret != -1) {
fprintf(stderr, "bogus recv(2) return value %zd (expected %zd)\n",
ret, expected_ret);
exit(EXIT_FAILURE);
}
if (errno != -expected_ret) {
perror("recv");
exit(EXIT_FAILURE);
}
return;
}
if (ret < 0) {
perror("recv");
exit(EXIT_FAILURE);
}
if (nread != expected_ret) {
if (ret == 0)
fprintf(stderr, "unexpected EOF while receiving bytes\n");
fprintf(stderr, "bogus recv(2) bytes read %zd (expected %zd)\n",
nread, expected_ret);
exit(EXIT_FAILURE);
}
}
/* Transmit one byte and check the return value.
*
* expected_ret:
* <0 Negative errno (for testing errors)
* 0 End-of-file
* 1 Success
*/
void send_byte(int fd, int expected_ret, int flags)
{
static const uint8_t byte = 'A';
send_buf(fd, &byte, sizeof(byte), flags, expected_ret);
}
/* Receive one byte and check the return value.
*
* expected_ret:
* <0 Negative errno (for testing errors)
* 0 End-of-file
* 1 Success
*/
void recv_byte(int fd, int expected_ret, int flags)
{
uint8_t byte;
recv_buf(fd, &byte, sizeof(byte), flags, expected_ret);
if (byte != 'A') {
fprintf(stderr, "unexpected byte read 0x%02x\n", byte);
exit(EXIT_FAILURE);
}
}
/* Run test cases. The program terminates if a failure occurs. */
void run_tests(const struct test_case *test_cases,
const struct test_opts *opts)
{
int i;
for (i = 0; test_cases[i].name; i++) {
void (*run)(const struct test_opts *opts);
char *line;
printf("%d - %s...", i, test_cases[i].name);
fflush(stdout);
/* Full barrier before executing the next test. This
* ensures that client and server are executing the
* same test case. In particular, it means whoever is
* faster will not see the peer still executing the
* last test. This is important because port numbers
* can be used by multiple test cases.
*/
if (test_cases[i].skip)
control_writeln("SKIP");
else
control_writeln("NEXT");
line = control_readln();
if (control_cmpln(line, "SKIP", false) || test_cases[i].skip) {
printf("skipped\n");
free(line);
continue;
}
control_cmpln(line, "NEXT", true);
free(line);
if (opts->mode == TEST_MODE_CLIENT)
run = test_cases[i].run_client;
else
run = test_cases[i].run_server;
if (run)
run(opts);
printf("ok\n");
}
printf("All tests have been executed. Waiting other peer...");
fflush(stdout);
/*
* Final full barrier, to ensure that all tests have been run and
* that even the last one has been successful on both sides.
*/
control_writeln("COMPLETED");
control_expectln("COMPLETED");
printf("ok\n");
}
void list_tests(const struct test_case *test_cases)
{
int i;
printf("ID\tTest name\n");
for (i = 0; test_cases[i].name; i++)
printf("%d\t%s\n", i, test_cases[i].name);
exit(EXIT_FAILURE);
}
static unsigned long parse_test_id(const char *test_id_str, size_t test_cases_len)
{
unsigned long test_id;
char *endptr = NULL;
errno = 0;
test_id = strtoul(test_id_str, &endptr, 10);
if (errno || *endptr != '\0') {
fprintf(stderr, "malformed test ID \"%s\"\n", test_id_str);
exit(EXIT_FAILURE);
}
if (test_id >= test_cases_len) {
fprintf(stderr, "test ID (%lu) larger than the max allowed (%lu)\n",
test_id, test_cases_len - 1);
exit(EXIT_FAILURE);
}
return test_id;
}
void skip_test(struct test_case *test_cases, size_t test_cases_len,
const char *test_id_str)
{
unsigned long test_id = parse_test_id(test_id_str, test_cases_len);
test_cases[test_id].skip = true;
}
void pick_test(struct test_case *test_cases, size_t test_cases_len,
const char *test_id_str)
{
static bool skip_all = true;
unsigned long test_id;
if (skip_all) {
unsigned long i;
for (i = 0; i < test_cases_len; ++i)
test_cases[i].skip = true;
skip_all = false;
}
test_id = parse_test_id(test_id_str, test_cases_len);
test_cases[test_id].skip = false;
}
unsigned long hash_djb2(const void *data, size_t len)
{
unsigned long hash = 5381;
int i = 0;
while (i < len) {
hash = ((hash << 5) + hash) + ((unsigned char *)data)[i];
i++;
}
return hash;
}
size_t iovec_bytes(const struct iovec *iov, size_t iovnum)
{
size_t bytes;
int i;
for (bytes = 0, i = 0; i < iovnum; i++)
bytes += iov[i].iov_len;
return bytes;
}
unsigned long iovec_hash_djb2(const struct iovec *iov, size_t iovnum)
{
unsigned long hash;
size_t iov_bytes;
size_t offs;
void *tmp;
int i;
iov_bytes = iovec_bytes(iov, iovnum);
tmp = malloc(iov_bytes);
if (!tmp) {
perror("malloc");
exit(EXIT_FAILURE);
}
for (offs = 0, i = 0; i < iovnum; i++) {
memcpy(tmp + offs, iov[i].iov_base, iov[i].iov_len);
offs += iov[i].iov_len;
}
hash = hash_djb2(tmp, iov_bytes);
free(tmp);
return hash;
}
/* Allocates and returns new 'struct iovec *' according pattern
* in the 'test_iovec'. For each element in the 'test_iovec' it
* allocates new element in the resulting 'iovec'. 'iov_len'
* of the new element is copied from 'test_iovec'. 'iov_base' is
* allocated depending on the 'iov_base' of 'test_iovec':
*
* 'iov_base' == NULL -> valid buf: mmap('iov_len').
*
* 'iov_base' == MAP_FAILED -> invalid buf:
* mmap('iov_len'), then munmap('iov_len').
* 'iov_base' still contains result of
* mmap().
*
* 'iov_base' == number -> unaligned valid buf:
* mmap('iov_len') + number.
*
* 'iovnum' is number of elements in 'test_iovec'.
*
* Returns new 'iovec' or calls 'exit()' on error.
*/
struct iovec *alloc_test_iovec(const struct iovec *test_iovec, int iovnum)
{
struct iovec *iovec;
int i;
iovec = malloc(sizeof(*iovec) * iovnum);
if (!iovec) {
perror("malloc");
exit(EXIT_FAILURE);
}
for (i = 0; i < iovnum; i++) {
iovec[i].iov_len = test_iovec[i].iov_len;
iovec[i].iov_base = mmap(NULL, iovec[i].iov_len,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE,
-1, 0);
if (iovec[i].iov_base == MAP_FAILED) {
perror("mmap");
exit(EXIT_FAILURE);
}
if (test_iovec[i].iov_base != MAP_FAILED)
iovec[i].iov_base += (uintptr_t)test_iovec[i].iov_base;
}
/* Unmap "invalid" elements. */
for (i = 0; i < iovnum; i++) {
if (test_iovec[i].iov_base == MAP_FAILED) {
if (munmap(iovec[i].iov_base, iovec[i].iov_len)) {
perror("munmap");
exit(EXIT_FAILURE);
}
}
}
for (i = 0; i < iovnum; i++) {
int j;
if (test_iovec[i].iov_base == MAP_FAILED)
continue;
for (j = 0; j < iovec[i].iov_len; j++)
((uint8_t *)iovec[i].iov_base)[j] = rand() & 0xff;
}
return iovec;
}
/* Frees 'iovec *', previously allocated by 'alloc_test_iovec()'.
* On error calls 'exit()'.
*/
void free_test_iovec(const struct iovec *test_iovec,
struct iovec *iovec, int iovnum)
{
int i;
for (i = 0; i < iovnum; i++) {
if (test_iovec[i].iov_base != MAP_FAILED) {
if (test_iovec[i].iov_base)
iovec[i].iov_base -= (uintptr_t)test_iovec[i].iov_base;
if (munmap(iovec[i].iov_base, iovec[i].iov_len)) {
perror("munmap");
exit(EXIT_FAILURE);
}
}
}
free(iovec);
}
/* Set "unsigned long long" socket option and check that it's indeed set */
void setsockopt_ull_check(int fd, int level, int optname,
unsigned long long val, char const *errmsg)
{
unsigned long long chkval;
socklen_t chklen;
int err;
err = setsockopt(fd, level, optname, &val, sizeof(val));
if (err) {
fprintf(stderr, "setsockopt err: %s (%d)\n",
strerror(errno), errno);
goto fail;
}
chkval = ~val; /* just make storage != val */
chklen = sizeof(chkval);
err = getsockopt(fd, level, optname, &chkval, &chklen);
if (err) {
fprintf(stderr, "getsockopt err: %s (%d)\n",
strerror(errno), errno);
goto fail;
}
if (chklen != sizeof(chkval)) {
fprintf(stderr, "size mismatch: set %zu got %d\n", sizeof(val),
chklen);
goto fail;
}
if (chkval != val) {
fprintf(stderr, "value mismatch: set %llu got %llu\n", val,
chkval);
goto fail;
}
return;
fail:
fprintf(stderr, "%s val %llu\n", errmsg, val);
exit(EXIT_FAILURE);
}
/* Set "int" socket option and check that it's indeed set */
void setsockopt_int_check(int fd, int level, int optname, int val,
char const *errmsg)
{
int chkval;
socklen_t chklen;
int err;
err = setsockopt(fd, level, optname, &val, sizeof(val));
if (err) {
fprintf(stderr, "setsockopt err: %s (%d)\n",
strerror(errno), errno);
goto fail;
}
chkval = ~val; /* just make storage != val */
chklen = sizeof(chkval);
err = getsockopt(fd, level, optname, &chkval, &chklen);
if (err) {
fprintf(stderr, "getsockopt err: %s (%d)\n",
strerror(errno), errno);
goto fail;
}
if (chklen != sizeof(chkval)) {
fprintf(stderr, "size mismatch: set %zu got %d\n", sizeof(val),
chklen);
goto fail;
}
if (chkval != val) {
fprintf(stderr, "value mismatch: set %d got %d\n", val, chkval);
goto fail;
}
return;
fail:
fprintf(stderr, "%s val %d\n", errmsg, val);
exit(EXIT_FAILURE);
}
static void mem_invert(unsigned char *mem, size_t size)
{
size_t i;
for (i = 0; i < size; i++)
mem[i] = ~mem[i];
}
/* Set "timeval" socket option and check that it's indeed set */
void setsockopt_timeval_check(int fd, int level, int optname,
struct timeval val, char const *errmsg)
{
struct timeval chkval;
socklen_t chklen;
int err;
err = setsockopt(fd, level, optname, &val, sizeof(val));
if (err) {
fprintf(stderr, "setsockopt err: %s (%d)\n",
strerror(errno), errno);
goto fail;
}
/* just make storage != val */
chkval = val;
mem_invert((unsigned char *)&chkval, sizeof(chkval));
chklen = sizeof(chkval);
err = getsockopt(fd, level, optname, &chkval, &chklen);
if (err) {
fprintf(stderr, "getsockopt err: %s (%d)\n",
strerror(errno), errno);
goto fail;
}
if (chklen != sizeof(chkval)) {
fprintf(stderr, "size mismatch: set %zu got %d\n", sizeof(val),
chklen);
goto fail;
}
if (memcmp(&chkval, &val, sizeof(val)) != 0) {
fprintf(stderr, "value mismatch: set %ld:%ld got %ld:%ld\n",
val.tv_sec, val.tv_usec, chkval.tv_sec, chkval.tv_usec);
goto fail;
}
return;
fail:
fprintf(stderr, "%s val %ld:%ld\n", errmsg, val.tv_sec, val.tv_usec);
exit(EXIT_FAILURE);
}
void enable_so_zerocopy_check(int fd)
{
setsockopt_int_check(fd, SOL_SOCKET, SO_ZEROCOPY, 1,
"setsockopt SO_ZEROCOPY");
}
void enable_so_linger(int fd, int timeout)
{
struct linger optval = {
.l_onoff = 1,
.l_linger = timeout
};
if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &optval, sizeof(optval))) {
perror("setsockopt(SO_LINGER)");
exit(EXIT_FAILURE);
}
}
static int __get_transports(void)
{
char buf[KALLSYMS_LINE_LEN];
const char *ksym;
int ret = 0;
FILE *f;
f = fopen(KALLSYMS_PATH, "r");
if (!f) {
perror("Can't open " KALLSYMS_PATH);
exit(EXIT_FAILURE);
}
while (fgets(buf, sizeof(buf), f)) {
char *match;
int i;
assert(buf[strlen(buf) - 1] == '\n');
for (i = 0; i < TRANSPORT_NUM; ++i) {
if (ret & BIT(i))
continue;
/* Match should be followed by '\t' or '\n'.
* See kallsyms.c:s_show().
*/
ksym = transport_ksyms[i];
match = strstr(buf, ksym);
if (match && isspace(match[strlen(ksym)])) {
ret |= BIT(i);
break;
}
}
}
fclose(f);
return ret;
}
/* Return integer with TRANSPORT_* bit set for every (known) registered vsock
* transport.
*/
int get_transports(void)
{
static int tr = -1;
if (tr == -1)
tr = __get_transports();
return tr;
}
+119
View File
@@ -0,0 +1,119 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef UTIL_H
#define UTIL_H
#include <sys/socket.h>
#include <linux/bitops.h>
#include <linux/kernel.h>
#include <linux/vm_sockets.h>
/* All known vsock transports, see callers of vsock_core_register() */
#define KNOWN_TRANSPORTS(x) \
x(LOOPBACK, "loopback") \
x(VIRTIO, "virtio") \
x(VHOST, "vhost") \
x(VMCI, "vmci") \
x(HYPERV, "hvs")
enum transport {
TRANSPORT_COUNTER_BASE = __COUNTER__ + 1,
#define x(name, symbol) \
TRANSPORT_##name = BIT(__COUNTER__ - TRANSPORT_COUNTER_BASE),
KNOWN_TRANSPORTS(x)
TRANSPORT_NUM = __COUNTER__ - TRANSPORT_COUNTER_BASE,
#undef x
};
static const char * const transport_ksyms[] = {
#define x(name, symbol) " " symbol "_transport",
KNOWN_TRANSPORTS(x)
#undef x
};
static_assert(ARRAY_SIZE(transport_ksyms) == TRANSPORT_NUM);
static_assert(BITS_PER_TYPE(int) >= TRANSPORT_NUM);
#define TRANSPORTS_G2H (TRANSPORT_VIRTIO | TRANSPORT_VMCI | TRANSPORT_HYPERV)
#define TRANSPORTS_H2G (TRANSPORT_VHOST | TRANSPORT_VMCI)
#define TRANSPORTS_LOCAL (TRANSPORT_LOOPBACK)
/* Tests can either run as the client or the server */
enum test_mode {
TEST_MODE_UNSET,
TEST_MODE_CLIENT,
TEST_MODE_SERVER
};
#define DEFAULT_PEER_PORT 1234
/* Test runner options */
struct test_opts {
enum test_mode mode;
unsigned int peer_cid;
unsigned int peer_port;
};
/* A test case definition. Test functions must print failures to stderr and
* terminate with exit(EXIT_FAILURE).
*/
struct test_case {
const char *name; /* human-readable name */
/* Called when test mode is TEST_MODE_CLIENT */
void (*run_client)(const struct test_opts *opts);
/* Called when test mode is TEST_MODE_SERVER */
void (*run_server)(const struct test_opts *opts);
bool skip;
};
void init_signals(void);
unsigned int parse_cid(const char *str);
unsigned int parse_port(const char *str);
int vsock_connect_fd(int fd, unsigned int cid, unsigned int port);
int vsock_connect(unsigned int cid, unsigned int port, int type);
int vsock_accept(unsigned int cid, unsigned int port,
struct sockaddr_vm *clientaddrp, int type);
int vsock_stream_connect(unsigned int cid, unsigned int port);
int vsock_bind_try(unsigned int cid, unsigned int port, int type);
int vsock_bind(unsigned int cid, unsigned int port, int type);
int vsock_bind_connect(unsigned int cid, unsigned int port,
unsigned int bind_port, int type);
int vsock_seqpacket_connect(unsigned int cid, unsigned int port);
int vsock_stream_accept(unsigned int cid, unsigned int port,
struct sockaddr_vm *clientaddrp);
int vsock_stream_listen(unsigned int cid, unsigned int port);
int vsock_seqpacket_accept(unsigned int cid, unsigned int port,
struct sockaddr_vm *clientaddrp);
void vsock_wait_remote_close(int fd);
bool vsock_ioctl_int(int fd, unsigned long op, int expected);
bool vsock_wait_sent(int fd);
void send_buf(int fd, const void *buf, size_t len, int flags,
ssize_t expected_ret);
void recv_buf(int fd, void *buf, size_t len, int flags, ssize_t expected_ret);
void send_byte(int fd, int expected_ret, int flags);
void recv_byte(int fd, int expected_ret, int flags);
void run_tests(const struct test_case *test_cases,
const struct test_opts *opts);
void list_tests(const struct test_case *test_cases);
void skip_test(struct test_case *test_cases, size_t test_cases_len,
const char *test_id_str);
void pick_test(struct test_case *test_cases, size_t test_cases_len,
const char *test_id_str);
unsigned long hash_djb2(const void *data, size_t len);
size_t iovec_bytes(const struct iovec *iov, size_t iovnum);
unsigned long iovec_hash_djb2(const struct iovec *iov, size_t iovnum);
struct iovec *alloc_test_iovec(const struct iovec *test_iovec, int iovnum);
void free_test_iovec(const struct iovec *test_iovec,
struct iovec *iovec, int iovnum);
void setsockopt_ull_check(int fd, int level, int optname,
unsigned long long val, char const *errmsg);
void setsockopt_int_check(int fd, int level, int optname, int val,
char const *errmsg);
void setsockopt_timeval_check(int fd, int level, int optname,
struct timeval val, char const *errmsg);
void enable_so_zerocopy_check(int fd);
void enable_so_linger(int fd, int timeout);
int get_transports(void);
#endif /* UTIL_H */
@@ -0,0 +1,596 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* vsock_diag_test - vsock_diag.ko test suite
*
* Copyright (C) 2017 Red Hat, Inc.
*
* Author: Stefan Hajnoczi <stefanha@redhat.com>
*/
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <linux/list.h>
#include <linux/net.h>
#include <linux/netlink.h>
#include <linux/sock_diag.h>
#include <linux/vm_sockets_diag.h>
#include <netinet/tcp.h>
#include "timeout.h"
#include "control.h"
#include "util.h"
/* Per-socket status */
struct vsock_stat {
struct list_head list;
struct vsock_diag_msg msg;
};
static const char *sock_type_str(int type)
{
switch (type) {
case SOCK_DGRAM:
return "DGRAM";
case SOCK_STREAM:
return "STREAM";
case SOCK_SEQPACKET:
return "SEQPACKET";
default:
return "INVALID TYPE";
}
}
static const char *sock_state_str(int state)
{
switch (state) {
case TCP_CLOSE:
return "UNCONNECTED";
case TCP_SYN_SENT:
return "CONNECTING";
case TCP_ESTABLISHED:
return "CONNECTED";
case TCP_CLOSING:
return "DISCONNECTING";
case TCP_LISTEN:
return "LISTEN";
default:
return "INVALID STATE";
}
}
static const char *sock_shutdown_str(int shutdown)
{
switch (shutdown) {
case 1:
return "RCV_SHUTDOWN";
case 2:
return "SEND_SHUTDOWN";
case 3:
return "RCV_SHUTDOWN | SEND_SHUTDOWN";
default:
return "0";
}
}
static void print_vsock_addr(FILE *fp, unsigned int cid, unsigned int port)
{
if (cid == VMADDR_CID_ANY)
fprintf(fp, "*:");
else
fprintf(fp, "%u:", cid);
if (port == VMADDR_PORT_ANY)
fprintf(fp, "*");
else
fprintf(fp, "%u", port);
}
static void print_vsock_stat(FILE *fp, struct vsock_stat *st)
{
print_vsock_addr(fp, st->msg.vdiag_src_cid, st->msg.vdiag_src_port);
fprintf(fp, " ");
print_vsock_addr(fp, st->msg.vdiag_dst_cid, st->msg.vdiag_dst_port);
fprintf(fp, " %s %s %s %u\n",
sock_type_str(st->msg.vdiag_type),
sock_state_str(st->msg.vdiag_state),
sock_shutdown_str(st->msg.vdiag_shutdown),
st->msg.vdiag_ino);
}
static void print_vsock_stats(FILE *fp, struct list_head *head)
{
struct vsock_stat *st;
list_for_each_entry(st, head, list)
print_vsock_stat(fp, st);
}
static struct vsock_stat *find_vsock_stat(struct list_head *head, int fd)
{
struct vsock_stat *st;
struct stat stat;
if (fstat(fd, &stat) < 0) {
perror("fstat");
exit(EXIT_FAILURE);
}
list_for_each_entry(st, head, list)
if (st->msg.vdiag_ino == stat.st_ino)
return st;
fprintf(stderr, "cannot find fd %d\n", fd);
exit(EXIT_FAILURE);
}
static void check_no_sockets(struct list_head *head)
{
if (!list_empty(head)) {
fprintf(stderr, "expected no sockets\n");
print_vsock_stats(stderr, head);
exit(1);
}
}
static void check_num_sockets(struct list_head *head, int expected)
{
struct list_head *node;
int n = 0;
list_for_each(node, head)
n++;
if (n != expected) {
fprintf(stderr, "expected %d sockets, found %d\n",
expected, n);
print_vsock_stats(stderr, head);
exit(EXIT_FAILURE);
}
}
static void check_socket_state(struct vsock_stat *st, __u8 state)
{
if (st->msg.vdiag_state != state) {
fprintf(stderr, "expected socket state %#x, got %#x\n",
state, st->msg.vdiag_state);
exit(EXIT_FAILURE);
}
}
static void send_req(int fd)
{
struct sockaddr_nl nladdr = {
.nl_family = AF_NETLINK,
};
struct {
struct nlmsghdr nlh;
struct vsock_diag_req vreq;
} req = {
.nlh = {
.nlmsg_len = sizeof(req),
.nlmsg_type = SOCK_DIAG_BY_FAMILY,
.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,
},
.vreq = {
.sdiag_family = AF_VSOCK,
.vdiag_states = ~(__u32)0,
},
};
struct iovec iov = {
.iov_base = &req,
.iov_len = sizeof(req),
};
struct msghdr msg = {
.msg_name = &nladdr,
.msg_namelen = sizeof(nladdr),
.msg_iov = &iov,
.msg_iovlen = 1,
};
for (;;) {
if (sendmsg(fd, &msg, 0) < 0) {
if (errno == EINTR)
continue;
perror("sendmsg");
exit(EXIT_FAILURE);
}
return;
}
}
static ssize_t recv_resp(int fd, void *buf, size_t len)
{
struct sockaddr_nl nladdr = {
.nl_family = AF_NETLINK,
};
struct iovec iov = {
.iov_base = buf,
.iov_len = len,
};
struct msghdr msg = {
.msg_name = &nladdr,
.msg_namelen = sizeof(nladdr),
.msg_iov = &iov,
.msg_iovlen = 1,
};
ssize_t ret;
do {
ret = recvmsg(fd, &msg, 0);
} while (ret < 0 && errno == EINTR);
if (ret < 0) {
perror("recvmsg");
exit(EXIT_FAILURE);
}
return ret;
}
static void add_vsock_stat(struct list_head *sockets,
const struct vsock_diag_msg *resp)
{
struct vsock_stat *st;
st = malloc(sizeof(*st));
if (!st) {
perror("malloc");
exit(EXIT_FAILURE);
}
st->msg = *resp;
list_add_tail(&st->list, sockets);
}
/*
* Read vsock stats into a list.
*/
static void read_vsock_stat(struct list_head *sockets)
{
long buf[8192 / sizeof(long)];
int fd;
fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG);
if (fd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
send_req(fd);
for (;;) {
const struct nlmsghdr *h;
ssize_t ret;
ret = recv_resp(fd, buf, sizeof(buf));
if (ret == 0)
goto done;
if (ret < sizeof(*h)) {
fprintf(stderr, "short read of %zd bytes\n", ret);
exit(EXIT_FAILURE);
}
h = (struct nlmsghdr *)buf;
while (NLMSG_OK(h, ret)) {
if (h->nlmsg_type == NLMSG_DONE)
goto done;
if (h->nlmsg_type == NLMSG_ERROR) {
const struct nlmsgerr *err = NLMSG_DATA(h);
if (h->nlmsg_len < NLMSG_LENGTH(sizeof(*err)))
fprintf(stderr, "NLMSG_ERROR\n");
else {
errno = -err->error;
perror("NLMSG_ERROR");
}
exit(EXIT_FAILURE);
}
if (h->nlmsg_type != SOCK_DIAG_BY_FAMILY) {
fprintf(stderr, "unexpected nlmsg_type %#x\n",
h->nlmsg_type);
exit(EXIT_FAILURE);
}
if (h->nlmsg_len <
NLMSG_LENGTH(sizeof(struct vsock_diag_msg))) {
fprintf(stderr, "short vsock_diag_msg\n");
exit(EXIT_FAILURE);
}
add_vsock_stat(sockets, NLMSG_DATA(h));
h = NLMSG_NEXT(h, ret);
}
}
done:
close(fd);
}
static void free_sock_stat(struct list_head *sockets)
{
struct vsock_stat *st;
struct vsock_stat *next;
list_for_each_entry_safe(st, next, sockets, list)
free(st);
}
static void test_no_sockets(const struct test_opts *opts)
{
LIST_HEAD(sockets);
read_vsock_stat(&sockets);
check_no_sockets(&sockets);
}
static void test_listen_socket_server(const struct test_opts *opts)
{
union {
struct sockaddr sa;
struct sockaddr_vm svm;
} addr = {
.svm = {
.svm_family = AF_VSOCK,
.svm_port = opts->peer_port,
.svm_cid = VMADDR_CID_ANY,
},
};
LIST_HEAD(sockets);
struct vsock_stat *st;
int fd;
fd = socket(AF_VSOCK, SOCK_STREAM, 0);
if (bind(fd, &addr.sa, sizeof(addr.svm)) < 0) {
perror("bind");
exit(EXIT_FAILURE);
}
if (listen(fd, 1) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
read_vsock_stat(&sockets);
check_num_sockets(&sockets, 1);
st = find_vsock_stat(&sockets, fd);
check_socket_state(st, TCP_LISTEN);
close(fd);
free_sock_stat(&sockets);
}
static void test_connect_client(const struct test_opts *opts)
{
int fd;
LIST_HEAD(sockets);
struct vsock_stat *st;
fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);
if (fd < 0) {
perror("connect");
exit(EXIT_FAILURE);
}
read_vsock_stat(&sockets);
check_num_sockets(&sockets, 1);
st = find_vsock_stat(&sockets, fd);
check_socket_state(st, TCP_ESTABLISHED);
control_expectln("DONE");
control_writeln("DONE");
close(fd);
free_sock_stat(&sockets);
}
static void test_connect_server(const struct test_opts *opts)
{
struct vsock_stat *st;
LIST_HEAD(sockets);
int client_fd;
client_fd = vsock_stream_accept(VMADDR_CID_ANY, opts->peer_port, NULL);
if (client_fd < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
read_vsock_stat(&sockets);
check_num_sockets(&sockets, 1);
st = find_vsock_stat(&sockets, client_fd);
check_socket_state(st, TCP_ESTABLISHED);
control_writeln("DONE");
control_expectln("DONE");
close(client_fd);
free_sock_stat(&sockets);
}
static struct test_case test_cases[] = {
{
.name = "No sockets",
.run_server = test_no_sockets,
},
{
.name = "Listen socket",
.run_server = test_listen_socket_server,
},
{
.name = "Connect",
.run_client = test_connect_client,
.run_server = test_connect_server,
},
{},
};
static const char optstring[] = "";
static const struct option longopts[] = {
{
.name = "control-host",
.has_arg = required_argument,
.val = 'H',
},
{
.name = "control-port",
.has_arg = required_argument,
.val = 'P',
},
{
.name = "mode",
.has_arg = required_argument,
.val = 'm',
},
{
.name = "peer-cid",
.has_arg = required_argument,
.val = 'p',
},
{
.name = "peer-port",
.has_arg = required_argument,
.val = 'q',
},
{
.name = "list",
.has_arg = no_argument,
.val = 'l',
},
{
.name = "skip",
.has_arg = required_argument,
.val = 's',
},
{
.name = "help",
.has_arg = no_argument,
.val = '?',
},
{},
};
static void usage(void)
{
fprintf(stderr, "Usage: vsock_diag_test [--help] [--control-host=<host>] --control-port=<port> --mode=client|server --peer-cid=<cid> [--peer-port=<port>] [--list] [--skip=<test_id>]\n"
"\n"
" Server: vsock_diag_test --control-port=1234 --mode=server --peer-cid=3\n"
" Client: vsock_diag_test --control-host=192.168.0.1 --control-port=1234 --mode=client --peer-cid=2\n"
"\n"
"Run vsock_diag.ko tests. Must be launched in both\n"
"guest and host. One side must use --mode=client and\n"
"the other side must use --mode=server.\n"
"\n"
"A TCP control socket connection is used to coordinate tests\n"
"between the client and the server. The server requires a\n"
"listen address and the client requires an address to\n"
"connect to.\n"
"\n"
"The CID of the other side must be given with --peer-cid=<cid>.\n"
"\n"
"Options:\n"
" --help This help message\n"
" --control-host <host> Server IP address to connect to\n"
" --control-port <port> Server port to listen on/connect to\n"
" --mode client|server Server or client mode\n"
" --peer-cid <cid> CID of the other side\n"
" --peer-port <port> AF_VSOCK port used for the test [default: %d]\n"
" --list List of tests that will be executed\n"
" --skip <test_id> Test ID to skip;\n"
" use multiple --skip options to skip more tests\n",
DEFAULT_PEER_PORT
);
exit(EXIT_FAILURE);
}
int main(int argc, char **argv)
{
const char *control_host = NULL;
const char *control_port = NULL;
struct test_opts opts = {
.mode = TEST_MODE_UNSET,
.peer_cid = VMADDR_CID_ANY,
.peer_port = DEFAULT_PEER_PORT,
};
init_signals();
for (;;) {
int opt = getopt_long(argc, argv, optstring, longopts, NULL);
if (opt == -1)
break;
switch (opt) {
case 'H':
control_host = optarg;
break;
case 'm':
if (strcmp(optarg, "client") == 0)
opts.mode = TEST_MODE_CLIENT;
else if (strcmp(optarg, "server") == 0)
opts.mode = TEST_MODE_SERVER;
else {
fprintf(stderr, "--mode must be \"client\" or \"server\"\n");
return EXIT_FAILURE;
}
break;
case 'p':
opts.peer_cid = parse_cid(optarg);
break;
case 'q':
opts.peer_port = parse_port(optarg);
break;
case 'P':
control_port = optarg;
break;
case 'l':
list_tests(test_cases);
break;
case 's':
skip_test(test_cases, ARRAY_SIZE(test_cases) - 1,
optarg);
break;
case '?':
default:
usage();
}
}
if (!control_port)
usage();
if (opts.mode == TEST_MODE_UNSET)
usage();
if (opts.peer_cid == VMADDR_CID_ANY)
usage();
if (!control_host) {
if (opts.mode != TEST_MODE_SERVER)
usage();
control_host = "0.0.0.0";
}
control_init(control_host, control_port,
opts.mode == TEST_MODE_SERVER);
run_tests(test_cases, &opts);
control_cleanup();
return EXIT_SUCCESS;
}
@@ -0,0 +1,499 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* vsock_perf - benchmark utility for vsock.
*
* Copyright (C) 2022 SberDevices.
*
* Author: Arseniy Krasnov <AVKrasnov@sberdevices.ru>
*/
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <stdint.h>
#include <poll.h>
#include <sys/socket.h>
#include <linux/vm_sockets.h>
#include <sys/mman.h>
#include "msg_zerocopy_common.h"
#define DEFAULT_BUF_SIZE_BYTES (128 * 1024)
#define DEFAULT_TO_SEND_BYTES (64 * 1024)
#define DEFAULT_VSOCK_BUF_BYTES (256 * 1024)
#define DEFAULT_RCVLOWAT_BYTES 1
#define DEFAULT_PORT 1234
#define BYTES_PER_GB (1024 * 1024 * 1024ULL)
#define NSEC_PER_SEC (1000000000ULL)
static unsigned int port = DEFAULT_PORT;
static unsigned long buf_size_bytes = DEFAULT_BUF_SIZE_BYTES;
static unsigned long long vsock_buf_bytes = DEFAULT_VSOCK_BUF_BYTES;
static bool zerocopy;
static void error(const char *s)
{
perror(s);
exit(EXIT_FAILURE);
}
static time_t current_nsec(void)
{
struct timespec ts;
if (clock_gettime(CLOCK_REALTIME, &ts))
error("clock_gettime");
return (ts.tv_sec * NSEC_PER_SEC) + ts.tv_nsec;
}
/* From lib/cmdline.c. */
static unsigned long memparse(const char *ptr)
{
char *endptr;
unsigned long long ret = strtoull(ptr, &endptr, 0);
switch (*endptr) {
case 'E':
case 'e':
ret <<= 10;
case 'P':
case 'p':
ret <<= 10;
case 'T':
case 't':
ret <<= 10;
case 'G':
case 'g':
ret <<= 10;
case 'M':
case 'm':
ret <<= 10;
case 'K':
case 'k':
ret <<= 10;
endptr++;
default:
break;
}
return ret;
}
static void vsock_increase_buf_size(int fd)
{
if (setsockopt(fd, AF_VSOCK, SO_VM_SOCKETS_BUFFER_MAX_SIZE,
&vsock_buf_bytes, sizeof(vsock_buf_bytes)))
error("setsockopt(SO_VM_SOCKETS_BUFFER_MAX_SIZE)");
if (setsockopt(fd, AF_VSOCK, SO_VM_SOCKETS_BUFFER_SIZE,
&vsock_buf_bytes, sizeof(vsock_buf_bytes)))
error("setsockopt(SO_VM_SOCKETS_BUFFER_SIZE)");
}
static int vsock_connect(unsigned int cid, unsigned int port)
{
union {
struct sockaddr sa;
struct sockaddr_vm svm;
} addr = {
.svm = {
.svm_family = AF_VSOCK,
.svm_port = port,
.svm_cid = cid,
},
};
int fd;
fd = socket(AF_VSOCK, SOCK_STREAM, 0);
if (fd < 0) {
perror("socket");
return -1;
}
if (connect(fd, &addr.sa, sizeof(addr.svm)) < 0) {
perror("connect");
close(fd);
return -1;
}
return fd;
}
static float get_gbps(unsigned long bits, time_t ns_delta)
{
return ((float)bits / 1000000000ULL) /
((float)ns_delta / NSEC_PER_SEC);
}
static void run_receiver(int rcvlowat_bytes)
{
unsigned int read_cnt;
time_t rx_begin_ns;
time_t in_read_ns;
size_t total_recv;
int client_fd;
char *data;
int fd;
union {
struct sockaddr sa;
struct sockaddr_vm svm;
} addr = {
.svm = {
.svm_family = AF_VSOCK,
.svm_port = port,
.svm_cid = VMADDR_CID_ANY,
},
};
union {
struct sockaddr sa;
struct sockaddr_vm svm;
} clientaddr;
socklen_t clientaddr_len = sizeof(clientaddr.svm);
printf("Run as receiver\n");
printf("Listen port %u\n", port);
printf("RX buffer %lu bytes\n", buf_size_bytes);
printf("vsock buffer %llu bytes\n", vsock_buf_bytes);
printf("SO_RCVLOWAT %d bytes\n", rcvlowat_bytes);
fd = socket(AF_VSOCK, SOCK_STREAM, 0);
if (fd < 0)
error("socket");
if (bind(fd, &addr.sa, sizeof(addr.svm)) < 0)
error("bind");
if (listen(fd, 1) < 0)
error("listen");
client_fd = accept(fd, &clientaddr.sa, &clientaddr_len);
if (client_fd < 0)
error("accept");
vsock_increase_buf_size(client_fd);
if (setsockopt(client_fd, SOL_SOCKET, SO_RCVLOWAT,
&rcvlowat_bytes,
sizeof(rcvlowat_bytes)))
error("setsockopt(SO_RCVLOWAT)");
data = malloc(buf_size_bytes);
if (!data) {
fprintf(stderr, "'malloc()' failed\n");
exit(EXIT_FAILURE);
}
read_cnt = 0;
in_read_ns = 0;
total_recv = 0;
rx_begin_ns = current_nsec();
while (1) {
struct pollfd fds = { 0 };
fds.fd = client_fd;
fds.events = POLLIN | POLLERR |
POLLHUP | POLLRDHUP;
if (poll(&fds, 1, -1) < 0)
error("poll");
if (fds.revents & POLLERR) {
fprintf(stderr, "'poll()' error\n");
exit(EXIT_FAILURE);
}
if (fds.revents & POLLIN) {
ssize_t bytes_read;
time_t t;
t = current_nsec();
bytes_read = read(fds.fd, data, buf_size_bytes);
in_read_ns += (current_nsec() - t);
read_cnt++;
if (!bytes_read)
break;
if (bytes_read < 0) {
perror("read");
exit(EXIT_FAILURE);
}
total_recv += bytes_read;
}
if (fds.revents & (POLLHUP | POLLRDHUP))
break;
}
printf("total bytes received: %zu\n", total_recv);
printf("rx performance: %f Gbits/s\n",
get_gbps(total_recv * 8, current_nsec() - rx_begin_ns));
printf("total time in 'read()': %f sec\n", (float)in_read_ns / NSEC_PER_SEC);
printf("average time in 'read()': %f ns\n", (float)in_read_ns / read_cnt);
printf("POLLIN wakeups: %i\n", read_cnt);
free(data);
close(client_fd);
close(fd);
}
static void enable_so_zerocopy(int fd)
{
int val = 1;
if (setsockopt(fd, SOL_SOCKET, SO_ZEROCOPY, &val, sizeof(val))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
}
static void run_sender(int peer_cid, unsigned long to_send_bytes)
{
time_t tx_begin_ns;
time_t tx_total_ns;
size_t total_send;
time_t time_in_send;
void *data;
int fd;
if (zerocopy)
printf("Run as sender MSG_ZEROCOPY\n");
else
printf("Run as sender\n");
printf("Connect to %i:%u\n", peer_cid, port);
printf("Send %lu bytes\n", to_send_bytes);
printf("TX buffer %lu bytes\n", buf_size_bytes);
fd = vsock_connect(peer_cid, port);
if (fd < 0)
exit(EXIT_FAILURE);
if (zerocopy) {
enable_so_zerocopy(fd);
data = mmap(NULL, buf_size_bytes, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (data == MAP_FAILED) {
perror("mmap");
exit(EXIT_FAILURE);
}
} else {
data = malloc(buf_size_bytes);
if (!data) {
fprintf(stderr, "'malloc()' failed\n");
exit(EXIT_FAILURE);
}
}
memset(data, 0, buf_size_bytes);
total_send = 0;
time_in_send = 0;
tx_begin_ns = current_nsec();
while (total_send < to_send_bytes) {
ssize_t sent;
size_t rest_bytes;
time_t before;
rest_bytes = to_send_bytes - total_send;
before = current_nsec();
sent = send(fd, data, (rest_bytes > buf_size_bytes) ?
buf_size_bytes : rest_bytes,
zerocopy ? MSG_ZEROCOPY : 0);
time_in_send += (current_nsec() - before);
if (sent <= 0)
error("write");
total_send += sent;
if (zerocopy) {
struct pollfd fds = { 0 };
fds.fd = fd;
if (poll(&fds, 1, -1) < 0) {
perror("poll");
exit(EXIT_FAILURE);
}
if (!(fds.revents & POLLERR)) {
fprintf(stderr, "POLLERR expected\n");
exit(EXIT_FAILURE);
}
vsock_recv_completion(fd, NULL);
}
}
tx_total_ns = current_nsec() - tx_begin_ns;
printf("total bytes sent: %zu\n", total_send);
printf("tx performance: %f Gbits/s\n",
get_gbps(total_send * 8, time_in_send));
printf("total time in tx loop: %f sec\n",
(float)tx_total_ns / NSEC_PER_SEC);
printf("time in 'send()': %f sec\n",
(float)time_in_send / NSEC_PER_SEC);
close(fd);
if (zerocopy)
munmap(data, buf_size_bytes);
else
free(data);
}
static const char optstring[] = "";
static const struct option longopts[] = {
{
.name = "help",
.has_arg = no_argument,
.val = 'H',
},
{
.name = "sender",
.has_arg = required_argument,
.val = 'S',
},
{
.name = "port",
.has_arg = required_argument,
.val = 'P',
},
{
.name = "bytes",
.has_arg = required_argument,
.val = 'M',
},
{
.name = "buf-size",
.has_arg = required_argument,
.val = 'B',
},
{
.name = "vsk-size",
.has_arg = required_argument,
.val = 'V',
},
{
.name = "rcvlowat",
.has_arg = required_argument,
.val = 'R',
},
{
.name = "zerocopy",
.has_arg = no_argument,
.val = 'Z',
},
{},
};
static void usage(void)
{
printf("Usage: ./vsock_perf [--help] [options]\n"
"\n"
"This is benchmarking utility, to test vsock performance.\n"
"It runs in two modes: sender or receiver. In sender mode, it\n"
"connects to the specified CID and starts data transmission.\n"
"\n"
"Options:\n"
" --help This message\n"
" --sender <cid> Sender mode (receiver default)\n"
" <cid> of the receiver to connect to\n"
" --zerocopy Enable zerocopy (for sender mode only)\n"
" --port <port> Port (default %d)\n"
" --bytes <bytes>KMG Bytes to send (default %d)\n"
" --buf-size <bytes>KMG Data buffer size (default %d). In sender mode\n"
" it is the buffer size, passed to 'write()'. In\n"
" receiver mode it is the buffer size passed to 'read()'.\n"
" --vsk-size <bytes>KMG Socket buffer size (default %d)\n"
" --rcvlowat <bytes>KMG SO_RCVLOWAT value (default %d)\n"
"\n", DEFAULT_PORT, DEFAULT_TO_SEND_BYTES,
DEFAULT_BUF_SIZE_BYTES, DEFAULT_VSOCK_BUF_BYTES,
DEFAULT_RCVLOWAT_BYTES);
exit(EXIT_FAILURE);
}
static long strtolx(const char *arg)
{
long value;
char *end;
value = strtol(arg, &end, 10);
if (end != arg + strlen(arg))
usage();
return value;
}
int main(int argc, char **argv)
{
unsigned long to_send_bytes = DEFAULT_TO_SEND_BYTES;
int rcvlowat_bytes = DEFAULT_RCVLOWAT_BYTES;
int peer_cid = -1;
bool sender = false;
while (1) {
int opt = getopt_long(argc, argv, optstring, longopts, NULL);
if (opt == -1)
break;
switch (opt) {
case 'V': /* Peer buffer size. */
vsock_buf_bytes = memparse(optarg);
break;
case 'R': /* SO_RCVLOWAT value. */
rcvlowat_bytes = memparse(optarg);
break;
case 'P': /* Port to connect to. */
port = strtolx(optarg);
break;
case 'M': /* Bytes to send. */
to_send_bytes = memparse(optarg);
break;
case 'B': /* Size of rx/tx buffer. */
buf_size_bytes = memparse(optarg);
break;
case 'S': /* Sender mode. CID to connect to. */
peer_cid = strtolx(optarg);
sender = true;
break;
case 'H': /* Help. */
usage();
break;
case 'Z': /* Zerocopy. */
zerocopy = true;
break;
default:
usage();
}
}
if (!sender)
run_receiver(rcvlowat_bytes);
else
run_sender(peer_cid, to_send_bytes);
return 0;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,432 @@
// SPDX-License-Identifier: GPL-2.0-only
/* MSG_ZEROCOPY feature tests for vsock
*
* Copyright (C) 2023 SberDevices.
*
* Author: Arseniy Krasnov <avkrasnov@salutedevices.com>
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <poll.h>
#include <linux/errqueue.h>
#include <linux/kernel.h>
#include <linux/sockios.h>
#include <linux/time64.h>
#include <errno.h>
#include "control.h"
#include "timeout.h"
#include "vsock_test_zerocopy.h"
#include "msg_zerocopy_common.h"
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif
#define VSOCK_TEST_DATA_MAX_IOV 3
struct vsock_test_data {
/* This test case if for SOCK_STREAM only. */
bool stream_only;
/* Data must be zerocopied. This field is checked against
* field 'ee_code' of the 'struct sock_extended_err', which
* contains bit to detect that zerocopy transmission was
* fallbacked to copy mode.
*/
bool zerocopied;
/* Enable SO_ZEROCOPY option on the socket. Without enabled
* SO_ZEROCOPY, every MSG_ZEROCOPY transmission will behave
* like without MSG_ZEROCOPY flag.
*/
bool so_zerocopy;
/* 'errno' after 'sendmsg()' call. */
int sendmsg_errno;
/* Number of valid elements in 'vecs'. */
int vecs_cnt;
struct iovec vecs[VSOCK_TEST_DATA_MAX_IOV];
};
static struct vsock_test_data test_data_array[] = {
/* Last element has non-page aligned size. */
{
.zerocopied = true,
.so_zerocopy = true,
.sendmsg_errno = 0,
.vecs_cnt = 3,
{
{ NULL, PAGE_SIZE },
{ NULL, PAGE_SIZE },
{ NULL, 200 }
}
},
/* All elements have page aligned base and size. */
{
.zerocopied = true,
.so_zerocopy = true,
.sendmsg_errno = 0,
.vecs_cnt = 3,
{
{ NULL, PAGE_SIZE },
{ NULL, PAGE_SIZE * 2 },
{ NULL, PAGE_SIZE * 3 }
}
},
/* All elements have page aligned base and size. But
* data length is bigger than 64Kb.
*/
{
.zerocopied = true,
.so_zerocopy = true,
.sendmsg_errno = 0,
.vecs_cnt = 3,
{
{ NULL, PAGE_SIZE * 16 },
{ NULL, PAGE_SIZE * 16 },
{ NULL, PAGE_SIZE * 16 }
}
},
/* Middle element has both non-page aligned base and size. */
{
.zerocopied = true,
.so_zerocopy = true,
.sendmsg_errno = 0,
.vecs_cnt = 3,
{
{ NULL, PAGE_SIZE },
{ (void *)1, 100 },
{ NULL, PAGE_SIZE }
}
},
/* Middle element is unmapped. */
{
.zerocopied = false,
.so_zerocopy = true,
.sendmsg_errno = ENOMEM,
.vecs_cnt = 3,
{
{ NULL, PAGE_SIZE },
{ MAP_FAILED, PAGE_SIZE },
{ NULL, PAGE_SIZE }
}
},
/* Valid data, but SO_ZEROCOPY is off. This
* will trigger fallback to copy.
*/
{
.zerocopied = false,
.so_zerocopy = false,
.sendmsg_errno = 0,
.vecs_cnt = 1,
{
{ NULL, PAGE_SIZE }
}
},
/* Valid data, but message is bigger than peer's
* buffer, so this will trigger fallback to copy.
* This test is for SOCK_STREAM only, because
* for SOCK_SEQPACKET, 'sendmsg()' returns EMSGSIZE.
*/
{
.stream_only = true,
.zerocopied = false,
.so_zerocopy = true,
.sendmsg_errno = 0,
.vecs_cnt = 1,
{
{ NULL, 100 * PAGE_SIZE }
}
},
};
#define POLL_TIMEOUT_MS 100
static void test_client(const struct test_opts *opts,
const struct vsock_test_data *test_data,
bool sock_seqpacket)
{
struct pollfd fds = { 0 };
struct msghdr msg = { 0 };
ssize_t sendmsg_res;
struct iovec *iovec;
int fd;
if (sock_seqpacket)
fd = vsock_seqpacket_connect(opts->peer_cid, opts->peer_port);
else
fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);
if (fd < 0) {
perror("connect");
exit(EXIT_FAILURE);
}
if (test_data->so_zerocopy)
enable_so_zerocopy_check(fd);
iovec = alloc_test_iovec(test_data->vecs, test_data->vecs_cnt);
msg.msg_iov = iovec;
msg.msg_iovlen = test_data->vecs_cnt;
errno = 0;
sendmsg_res = sendmsg(fd, &msg, MSG_ZEROCOPY);
if (errno != test_data->sendmsg_errno) {
fprintf(stderr, "expected 'errno' == %i, got %i\n",
test_data->sendmsg_errno, errno);
exit(EXIT_FAILURE);
}
if (!errno) {
if (sendmsg_res != iovec_bytes(iovec, test_data->vecs_cnt)) {
fprintf(stderr, "expected 'sendmsg()' == %li, got %li\n",
iovec_bytes(iovec, test_data->vecs_cnt),
sendmsg_res);
exit(EXIT_FAILURE);
}
}
fds.fd = fd;
fds.events = 0;
if (poll(&fds, 1, POLL_TIMEOUT_MS) < 0) {
perror("poll");
exit(EXIT_FAILURE);
}
if (fds.revents & POLLERR) {
vsock_recv_completion(fd, &test_data->zerocopied);
} else if (test_data->so_zerocopy && !test_data->sendmsg_errno) {
/* If we don't have data in the error queue, but
* SO_ZEROCOPY was enabled and 'sendmsg()' was
* successful - this is an error.
*/
fprintf(stderr, "POLLERR expected\n");
exit(EXIT_FAILURE);
}
if (!test_data->sendmsg_errno)
control_writeulong(iovec_hash_djb2(iovec, test_data->vecs_cnt));
else
control_writeulong(0);
control_writeln("DONE");
free_test_iovec(test_data->vecs, iovec, test_data->vecs_cnt);
close(fd);
}
void test_stream_msgzcopy_client(const struct test_opts *opts)
{
int i;
for (i = 0; i < ARRAY_SIZE(test_data_array); i++)
test_client(opts, &test_data_array[i], false);
}
void test_seqpacket_msgzcopy_client(const struct test_opts *opts)
{
int i;
for (i = 0; i < ARRAY_SIZE(test_data_array); i++) {
if (test_data_array[i].stream_only)
continue;
test_client(opts, &test_data_array[i], true);
}
}
static void test_server(const struct test_opts *opts,
const struct vsock_test_data *test_data,
bool sock_seqpacket)
{
unsigned long remote_hash;
unsigned long local_hash;
ssize_t total_bytes_rec;
unsigned char *data;
size_t data_len;
int fd;
if (sock_seqpacket)
fd = vsock_seqpacket_accept(VMADDR_CID_ANY, opts->peer_port, NULL);
else
fd = vsock_stream_accept(VMADDR_CID_ANY, opts->peer_port, NULL);
if (fd < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
data_len = iovec_bytes(test_data->vecs, test_data->vecs_cnt);
data = malloc(data_len);
if (!data) {
perror("malloc");
exit(EXIT_FAILURE);
}
total_bytes_rec = 0;
while (total_bytes_rec != data_len) {
ssize_t bytes_rec;
bytes_rec = read(fd, data + total_bytes_rec,
data_len - total_bytes_rec);
if (bytes_rec <= 0)
break;
total_bytes_rec += bytes_rec;
}
if (test_data->sendmsg_errno == 0)
local_hash = hash_djb2(data, data_len);
else
local_hash = 0;
free(data);
/* Waiting for some result. */
remote_hash = control_readulong();
if (remote_hash != local_hash) {
fprintf(stderr, "hash mismatch\n");
exit(EXIT_FAILURE);
}
control_expectln("DONE");
close(fd);
}
void test_stream_msgzcopy_server(const struct test_opts *opts)
{
int i;
for (i = 0; i < ARRAY_SIZE(test_data_array); i++)
test_server(opts, &test_data_array[i], false);
}
void test_seqpacket_msgzcopy_server(const struct test_opts *opts)
{
int i;
for (i = 0; i < ARRAY_SIZE(test_data_array); i++) {
if (test_data_array[i].stream_only)
continue;
test_server(opts, &test_data_array[i], true);
}
}
void test_stream_msgzcopy_empty_errq_client(const struct test_opts *opts)
{
struct msghdr msg = { 0 };
char cmsg_data[128];
ssize_t res;
int fd;
fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);
if (fd < 0) {
perror("connect");
exit(EXIT_FAILURE);
}
msg.msg_control = cmsg_data;
msg.msg_controllen = sizeof(cmsg_data);
res = recvmsg(fd, &msg, MSG_ERRQUEUE);
if (res != -1) {
fprintf(stderr, "expected 'recvmsg(2)' failure, got %zi\n",
res);
exit(EXIT_FAILURE);
}
control_writeln("DONE");
close(fd);
}
void test_stream_msgzcopy_empty_errq_server(const struct test_opts *opts)
{
int fd;
fd = vsock_stream_accept(VMADDR_CID_ANY, opts->peer_port, NULL);
if (fd < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
control_expectln("DONE");
close(fd);
}
#define GOOD_COPY_LEN 128 /* net/vmw_vsock/virtio_transport_common.c */
void test_stream_msgzcopy_mangle_client(const struct test_opts *opts)
{
char sbuf1[PAGE_SIZE + 1], sbuf2[GOOD_COPY_LEN];
unsigned long hash;
struct pollfd fds;
int fd, i;
fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);
if (fd < 0) {
perror("connect");
exit(EXIT_FAILURE);
}
enable_so_zerocopy_check(fd);
memset(sbuf1, 'x', sizeof(sbuf1));
send_buf(fd, sbuf1, sizeof(sbuf1), 0, sizeof(sbuf1));
for (i = 0; i < sizeof(sbuf2); i++)
sbuf2[i] = rand() & 0xff;
send_buf(fd, sbuf2, sizeof(sbuf2), MSG_ZEROCOPY, sizeof(sbuf2));
hash = hash_djb2(sbuf2, sizeof(sbuf2));
control_writeulong(hash);
fds.fd = fd;
fds.events = 0;
if (poll(&fds, 1, TIMEOUT * MSEC_PER_SEC) != 1 ||
!(fds.revents & POLLERR)) {
perror("poll");
exit(EXIT_FAILURE);
}
close(fd);
}
void test_stream_msgzcopy_mangle_server(const struct test_opts *opts)
{
unsigned long local_hash, remote_hash;
char rbuf[PAGE_SIZE + 1];
int fd;
fd = vsock_stream_accept(VMADDR_CID_ANY, opts->peer_port, NULL);
if (fd < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
/* Wait, don't race the (buggy) skbs coalescence. */
vsock_ioctl_int(fd, SIOCINQ, PAGE_SIZE + 1 + GOOD_COPY_LEN);
/* Discard the first packet. */
recv_buf(fd, rbuf, PAGE_SIZE + 1, 0, PAGE_SIZE + 1);
recv_buf(fd, rbuf, GOOD_COPY_LEN, 0, GOOD_COPY_LEN);
remote_hash = control_readulong();
local_hash = hash_djb2(rbuf, GOOD_COPY_LEN);
if (local_hash != remote_hash) {
fprintf(stderr, "Data received corrupted\n");
exit(EXIT_FAILURE);
}
close(fd);
}
@@ -0,0 +1,18 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef VSOCK_TEST_ZEROCOPY_H
#define VSOCK_TEST_ZEROCOPY_H
#include "util.h"
void test_stream_msgzcopy_client(const struct test_opts *opts);
void test_stream_msgzcopy_server(const struct test_opts *opts);
void test_seqpacket_msgzcopy_client(const struct test_opts *opts);
void test_seqpacket_msgzcopy_server(const struct test_opts *opts);
void test_stream_msgzcopy_empty_errq_client(const struct test_opts *opts);
void test_stream_msgzcopy_empty_errq_server(const struct test_opts *opts);
void test_stream_msgzcopy_mangle_client(const struct test_opts *opts);
void test_stream_msgzcopy_mangle_server(const struct test_opts *opts);
#endif /* VSOCK_TEST_ZEROCOPY_H */
@@ -0,0 +1,353 @@
// SPDX-License-Identifier: GPL-2.0-only
/* io_uring tests for vsock
*
* Copyright (C) 2023 SberDevices.
*
* Author: Arseniy Krasnov <avkrasnov@salutedevices.com>
*/
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <liburing.h>
#include <unistd.h>
#include <sys/mman.h>
#include <linux/kernel.h>
#include <error.h>
#include "util.h"
#include "control.h"
#include "msg_zerocopy_common.h"
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif
#define RING_ENTRIES_NUM 4
#define VSOCK_TEST_DATA_MAX_IOV 3
struct vsock_io_uring_test {
/* Number of valid elements in 'vecs'. */
int vecs_cnt;
struct iovec vecs[VSOCK_TEST_DATA_MAX_IOV];
};
static struct vsock_io_uring_test test_data_array[] = {
/* All elements have page aligned base and size. */
{
.vecs_cnt = 3,
{
{ NULL, PAGE_SIZE },
{ NULL, 2 * PAGE_SIZE },
{ NULL, 3 * PAGE_SIZE },
}
},
/* Middle element has both non-page aligned base and size. */
{
.vecs_cnt = 3,
{
{ NULL, PAGE_SIZE },
{ (void *)1, 200 },
{ NULL, 3 * PAGE_SIZE },
}
}
};
static void vsock_io_uring_client(const struct test_opts *opts,
const struct vsock_io_uring_test *test_data,
bool msg_zerocopy)
{
struct io_uring_sqe *sqe;
struct io_uring_cqe *cqe;
struct io_uring ring;
struct iovec *iovec;
struct msghdr msg;
int fd;
fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);
if (fd < 0) {
perror("connect");
exit(EXIT_FAILURE);
}
if (msg_zerocopy)
enable_so_zerocopy_check(fd);
iovec = alloc_test_iovec(test_data->vecs, test_data->vecs_cnt);
if (io_uring_queue_init(RING_ENTRIES_NUM, &ring, 0))
error(1, errno, "io_uring_queue_init");
if (io_uring_register_buffers(&ring, iovec, test_data->vecs_cnt))
error(1, errno, "io_uring_register_buffers");
memset(&msg, 0, sizeof(msg));
msg.msg_iov = iovec;
msg.msg_iovlen = test_data->vecs_cnt;
sqe = io_uring_get_sqe(&ring);
if (msg_zerocopy)
io_uring_prep_sendmsg_zc(sqe, fd, &msg, 0);
else
io_uring_prep_sendmsg(sqe, fd, &msg, 0);
if (io_uring_submit(&ring) != 1)
error(1, errno, "io_uring_submit");
if (io_uring_wait_cqe(&ring, &cqe))
error(1, errno, "io_uring_wait_cqe");
io_uring_cqe_seen(&ring, cqe);
control_writeulong(iovec_hash_djb2(iovec, test_data->vecs_cnt));
control_writeln("DONE");
io_uring_queue_exit(&ring);
free_test_iovec(test_data->vecs, iovec, test_data->vecs_cnt);
close(fd);
}
static void vsock_io_uring_server(const struct test_opts *opts,
const struct vsock_io_uring_test *test_data)
{
unsigned long remote_hash;
unsigned long local_hash;
struct io_uring ring;
size_t data_len;
size_t recv_len;
void *data;
int fd;
fd = vsock_stream_accept(VMADDR_CID_ANY, opts->peer_port, NULL);
if (fd < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
data_len = iovec_bytes(test_data->vecs, test_data->vecs_cnt);
data = malloc(data_len);
if (!data) {
perror("malloc");
exit(EXIT_FAILURE);
}
if (io_uring_queue_init(RING_ENTRIES_NUM, &ring, 0))
error(1, errno, "io_uring_queue_init");
recv_len = 0;
while (recv_len < data_len) {
struct io_uring_sqe *sqe;
struct io_uring_cqe *cqe;
struct iovec iovec;
sqe = io_uring_get_sqe(&ring);
iovec.iov_base = data + recv_len;
iovec.iov_len = data_len;
io_uring_prep_readv(sqe, fd, &iovec, 1, 0);
if (io_uring_submit(&ring) != 1)
error(1, errno, "io_uring_submit");
if (io_uring_wait_cqe(&ring, &cqe))
error(1, errno, "io_uring_wait_cqe");
recv_len += cqe->res;
io_uring_cqe_seen(&ring, cqe);
}
if (recv_len != data_len) {
fprintf(stderr, "expected %zu, got %zu\n", data_len,
recv_len);
exit(EXIT_FAILURE);
}
local_hash = hash_djb2(data, data_len);
remote_hash = control_readulong();
if (remote_hash != local_hash) {
fprintf(stderr, "hash mismatch\n");
exit(EXIT_FAILURE);
}
control_expectln("DONE");
io_uring_queue_exit(&ring);
free(data);
}
void test_stream_uring_server(const struct test_opts *opts)
{
int i;
for (i = 0; i < ARRAY_SIZE(test_data_array); i++)
vsock_io_uring_server(opts, &test_data_array[i]);
}
void test_stream_uring_client(const struct test_opts *opts)
{
int i;
for (i = 0; i < ARRAY_SIZE(test_data_array); i++)
vsock_io_uring_client(opts, &test_data_array[i], false);
}
void test_stream_uring_msg_zc_server(const struct test_opts *opts)
{
int i;
for (i = 0; i < ARRAY_SIZE(test_data_array); i++)
vsock_io_uring_server(opts, &test_data_array[i]);
}
void test_stream_uring_msg_zc_client(const struct test_opts *opts)
{
int i;
for (i = 0; i < ARRAY_SIZE(test_data_array); i++)
vsock_io_uring_client(opts, &test_data_array[i], true);
}
static struct test_case test_cases[] = {
{
.name = "SOCK_STREAM io_uring test",
.run_server = test_stream_uring_server,
.run_client = test_stream_uring_client,
},
{
.name = "SOCK_STREAM io_uring MSG_ZEROCOPY test",
.run_server = test_stream_uring_msg_zc_server,
.run_client = test_stream_uring_msg_zc_client,
},
{},
};
static const char optstring[] = "";
static const struct option longopts[] = {
{
.name = "control-host",
.has_arg = required_argument,
.val = 'H',
},
{
.name = "control-port",
.has_arg = required_argument,
.val = 'P',
},
{
.name = "mode",
.has_arg = required_argument,
.val = 'm',
},
{
.name = "peer-cid",
.has_arg = required_argument,
.val = 'p',
},
{
.name = "peer-port",
.has_arg = required_argument,
.val = 'q',
},
{
.name = "help",
.has_arg = no_argument,
.val = '?',
},
{},
};
static void usage(void)
{
fprintf(stderr, "Usage: vsock_uring_test [--help] [--control-host=<host>] --control-port=<port> --mode=client|server --peer-cid=<cid> [--peer-port=<port>]\n"
"\n"
" Server: vsock_uring_test --control-port=1234 --mode=server --peer-cid=3\n"
" Client: vsock_uring_test --control-host=192.168.0.1 --control-port=1234 --mode=client --peer-cid=2\n"
"\n"
"Run transmission tests using io_uring. Usage is the same as\n"
"in ./vsock_test\n"
"\n"
"Options:\n"
" --help This help message\n"
" --control-host <host> Server IP address to connect to\n"
" --control-port <port> Server port to listen on/connect to\n"
" --mode client|server Server or client mode\n"
" --peer-cid <cid> CID of the other side\n"
" --peer-port <port> AF_VSOCK port used for the test [default: %d]\n",
DEFAULT_PEER_PORT
);
exit(EXIT_FAILURE);
}
int main(int argc, char **argv)
{
const char *control_host = NULL;
const char *control_port = NULL;
struct test_opts opts = {
.mode = TEST_MODE_UNSET,
.peer_cid = VMADDR_CID_ANY,
.peer_port = DEFAULT_PEER_PORT,
};
init_signals();
for (;;) {
int opt = getopt_long(argc, argv, optstring, longopts, NULL);
if (opt == -1)
break;
switch (opt) {
case 'H':
control_host = optarg;
break;
case 'm':
if (strcmp(optarg, "client") == 0) {
opts.mode = TEST_MODE_CLIENT;
} else if (strcmp(optarg, "server") == 0) {
opts.mode = TEST_MODE_SERVER;
} else {
fprintf(stderr, "--mode must be \"client\" or \"server\"\n");
return EXIT_FAILURE;
}
break;
case 'p':
opts.peer_cid = parse_cid(optarg);
break;
case 'q':
opts.peer_port = parse_port(optarg);
break;
case 'P':
control_port = optarg;
break;
case '?':
default:
usage();
}
}
if (!control_port)
usage();
if (opts.mode == TEST_MODE_UNSET)
usage();
if (opts.peer_cid == VMADDR_CID_ANY)
usage();
if (!control_host) {
if (opts.mode != TEST_MODE_SERVER)
usage();
control_host = "0.0.0.0";
}
control_init(control_host, control_port,
opts.mode == TEST_MODE_SERVER);
run_tests(test_cases, &opts);
control_cleanup();
return 0;
}