Merge branch 'VSOCK-add-vsock_test-test-suite'
Stefano Garzarella says: ==================== VSOCK: add vsock_test test suite The vsock_diag.ko module already has a test suite but the core AF_VSOCK functionality has no tests. This patch series adds several test cases that exercise AF_VSOCK SOCK_STREAM socket semantics (send/recv, connect/accept, half-closed connections, simultaneous connections). The v1 of this series was originally sent by Stefan. v3: - Patch 6: * check the byte received in the recv_byte() * use send(2)/recv(2) instead of write(2)/read(2) to test also flags (e.g. MSG_PEEK) - Patch 8: * removed unnecessary control_expectln("CLOSED") [Stefan]. - removed patches 9,10,11 added in the v2 - new Patch 9 add parameters to list and skip tests (e.g. useful for vmci that doesn't support half-closed socket in the host) - new Patch 10 prints a list of options in the help - new Patch 11 tests MSG_PEEK flags of recv(2) v2: https://patchwork.ozlabs.org/cover/1140538/ v1: https://patchwork.ozlabs.org/cover/847998/ ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
commit
17338900cc
1
tools/testing/vsock/.gitignore
vendored
1
tools/testing/vsock/.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
*.d
|
||||
vsock_test
|
||||
vsock_diag_test
|
||||
|
@ -1,10 +1,11 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
all: test
|
||||
test: vsock_diag_test
|
||||
vsock_diag_test: vsock_diag_test.o timeout.o control.o
|
||||
test: vsock_test vsock_diag_test
|
||||
vsock_test: vsock_test.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/uapi -I../../include -Wno-pointer-sign -fno-strict-overflow -fno-strict-aliasing -fno-common -MMD -U_FORTIFY_SOURCE -D_GNU_SOURCE
|
||||
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_diag_test
|
||||
${RM} *.o *.d vsock_test vsock_diag_test
|
||||
-include *.d
|
||||
|
@ -5,12 +5,13 @@ 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 and these 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.
|
||||
|
@ -205,11 +205,22 @@ void control_expectln(const char *str)
|
||||
char *line;
|
||||
|
||||
line = control_readln();
|
||||
if (strcmp(str, line) != 0) {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
free(line);
|
||||
return false;
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
#ifndef CONTROL_H
|
||||
#define CONTROL_H
|
||||
|
||||
@ -9,5 +10,6 @@ void control_cleanup(void);
|
||||
void control_writeln(const char *str);
|
||||
char *control_readln(void);
|
||||
void control_expectln(const char *str);
|
||||
bool control_cmpln(char *line, const char *str, bool fail);
|
||||
|
||||
#endif /* CONTROL_H */
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
#ifndef TIMEOUT_H
|
||||
#define TIMEOUT_H
|
||||
|
||||
|
375
tools/testing/vsock/util.c
Normal file
375
tools/testing/vsock/util.c
Normal file
@ -0,0 +1,375 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* vsock test utilities
|
||||
*
|
||||
* Copyright (C) 2017 Red Hat, Inc.
|
||||
*
|
||||
* Author: Stefan Hajnoczi <stefanha@redhat.com>
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <sys/epoll.h>
|
||||
|
||||
#include "timeout.h"
|
||||
#include "control.h"
|
||||
#include "util.h"
|
||||
|
||||
/* Install signal handlers */
|
||||
void init_signals(void)
|
||||
{
|
||||
struct sigaction act = {
|
||||
.sa_handler = sigalrm,
|
||||
};
|
||||
|
||||
sigaction(SIGALRM, &act, NULL);
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
}
|
||||
|
||||
/* Parse a CID in string representation */
|
||||
unsigned int parse_cid(const char *str)
|
||||
{
|
||||
char *endptr = NULL;
|
||||
unsigned long n;
|
||||
|
||||
errno = 0;
|
||||
n = strtoul(str, &endptr, 10);
|
||||
if (errno || *endptr != '\0') {
|
||||
fprintf(stderr, "malformed CID \"%s\"\n", str);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
/* Connect to <cid, port> and return the file descriptor. */
|
||||
int vsock_stream_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 ret;
|
||||
int fd;
|
||||
|
||||
control_expectln("LISTENING");
|
||||
|
||||
fd = socket(AF_VSOCK, SOCK_STREAM, 0);
|
||||
|
||||
timeout_begin(TIMEOUT);
|
||||
do {
|
||||
ret = connect(fd, &addr.sa, sizeof(addr.svm));
|
||||
timeout_check("connect");
|
||||
} while (ret < 0 && errno == EINTR);
|
||||
timeout_end();
|
||||
|
||||
if (ret < 0) {
|
||||
int old_errno = errno;
|
||||
|
||||
close(fd);
|
||||
fd = -1;
|
||||
errno = old_errno;
|
||||
}
|
||||
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_stream_accept(unsigned int cid, unsigned int port,
|
||||
struct sockaddr_vm *clientaddrp)
|
||||
{
|
||||
union {
|
||||
struct sockaddr sa;
|
||||
struct sockaddr_vm svm;
|
||||
} addr = {
|
||||
.svm = {
|
||||
.svm_family = AF_VSOCK,
|
||||
.svm_port = port,
|
||||
.svm_cid = cid,
|
||||
},
|
||||
};
|
||||
union {
|
||||
struct sockaddr sa;
|
||||
struct sockaddr_vm svm;
|
||||
} clientaddr;
|
||||
socklen_t clientaddr_len = sizeof(clientaddr.svm);
|
||||
int fd;
|
||||
int client_fd;
|
||||
int old_errno;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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)
|
||||
{
|
||||
const uint8_t byte = 'A';
|
||||
ssize_t nwritten;
|
||||
|
||||
timeout_begin(TIMEOUT);
|
||||
do {
|
||||
nwritten = send(fd, &byte, sizeof(byte), flags);
|
||||
timeout_check("write");
|
||||
} while (nwritten < 0 && errno == EINTR);
|
||||
timeout_end();
|
||||
|
||||
if (expected_ret < 0) {
|
||||
if (nwritten != -1) {
|
||||
fprintf(stderr, "bogus send(2) return value %zd\n",
|
||||
nwritten);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (errno != -expected_ret) {
|
||||
perror("write");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (nwritten < 0) {
|
||||
perror("write");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (nwritten == 0) {
|
||||
if (expected_ret == 0)
|
||||
return;
|
||||
|
||||
fprintf(stderr, "unexpected EOF while sending byte\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (nwritten != sizeof(byte)) {
|
||||
fprintf(stderr, "bogus send(2) return value %zd\n", nwritten);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
ssize_t nread;
|
||||
|
||||
timeout_begin(TIMEOUT);
|
||||
do {
|
||||
nread = recv(fd, &byte, sizeof(byte), flags);
|
||||
timeout_check("read");
|
||||
} while (nread < 0 && errno == EINTR);
|
||||
timeout_end();
|
||||
|
||||
if (expected_ret < 0) {
|
||||
if (nread != -1) {
|
||||
fprintf(stderr, "bogus recv(2) return value %zd\n",
|
||||
nread);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (errno != -expected_ret) {
|
||||
perror("read");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (nread < 0) {
|
||||
perror("read");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (nread == 0) {
|
||||
if (expected_ret == 0)
|
||||
return;
|
||||
|
||||
fprintf(stderr, "unexpected EOF while receiving byte\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (nread != sizeof(byte)) {
|
||||
fprintf(stderr, "bogus recv(2) return value %zd\n", nread);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (byte != 'A') {
|
||||
fprintf(stderr, "unexpected byte read %c\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");
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void skip_test(struct test_case *test_cases, size_t test_cases_len,
|
||||
const char *test_id_str)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
test_cases[test_id].skip = true;
|
||||
}
|
49
tools/testing/vsock/util.h
Normal file
49
tools/testing/vsock/util.h
Normal file
@ -0,0 +1,49 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
#ifndef UTIL_H
|
||||
#define UTIL_H
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <linux/vm_sockets.h>
|
||||
|
||||
/* Tests can either run as the client or the server */
|
||||
enum test_mode {
|
||||
TEST_MODE_UNSET,
|
||||
TEST_MODE_CLIENT,
|
||||
TEST_MODE_SERVER
|
||||
};
|
||||
|
||||
/* Test runner options */
|
||||
struct test_opts {
|
||||
enum test_mode mode;
|
||||
unsigned int peer_cid;
|
||||
};
|
||||
|
||||
/* 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);
|
||||
int vsock_stream_connect(unsigned int cid, unsigned int port);
|
||||
int vsock_stream_accept(unsigned int cid, unsigned int port,
|
||||
struct sockaddr_vm *clientaddrp);
|
||||
void vsock_wait_remote_close(int fd);
|
||||
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);
|
||||
#endif /* UTIL_H */
|
@ -9,32 +9,22 @@
|
||||
|
||||
#include <getopt.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <sys/socket.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 "../../../include/uapi/linux/vm_sockets.h"
|
||||
#include "../../../include/uapi/linux/vm_sockets_diag.h"
|
||||
|
||||
#include "timeout.h"
|
||||
#include "control.h"
|
||||
|
||||
enum test_mode {
|
||||
TEST_MODE_UNSET,
|
||||
TEST_MODE_CLIENT,
|
||||
TEST_MODE_SERVER
|
||||
};
|
||||
#include "util.h"
|
||||
|
||||
/* Per-socket status */
|
||||
struct vsock_stat {
|
||||
@ -335,7 +325,7 @@ static void free_sock_stat(struct list_head *sockets)
|
||||
free(st);
|
||||
}
|
||||
|
||||
static void test_no_sockets(unsigned int peer_cid)
|
||||
static void test_no_sockets(const struct test_opts *opts)
|
||||
{
|
||||
LIST_HEAD(sockets);
|
||||
|
||||
@ -346,7 +336,7 @@ static void test_no_sockets(unsigned int peer_cid)
|
||||
free_sock_stat(&sockets);
|
||||
}
|
||||
|
||||
static void test_listen_socket_server(unsigned int peer_cid)
|
||||
static void test_listen_socket_server(const struct test_opts *opts)
|
||||
{
|
||||
union {
|
||||
struct sockaddr sa;
|
||||
@ -384,35 +374,14 @@ static void test_listen_socket_server(unsigned int peer_cid)
|
||||
free_sock_stat(&sockets);
|
||||
}
|
||||
|
||||
static void test_connect_client(unsigned int peer_cid)
|
||||
static void test_connect_client(const struct test_opts *opts)
|
||||
{
|
||||
union {
|
||||
struct sockaddr sa;
|
||||
struct sockaddr_vm svm;
|
||||
} addr = {
|
||||
.svm = {
|
||||
.svm_family = AF_VSOCK,
|
||||
.svm_port = 1234,
|
||||
.svm_cid = peer_cid,
|
||||
},
|
||||
};
|
||||
int fd;
|
||||
int ret;
|
||||
LIST_HEAD(sockets);
|
||||
struct vsock_stat *st;
|
||||
|
||||
control_expectln("LISTENING");
|
||||
|
||||
fd = socket(AF_VSOCK, SOCK_STREAM, 0);
|
||||
|
||||
timeout_begin(TIMEOUT);
|
||||
do {
|
||||
ret = connect(fd, &addr.sa, sizeof(addr.svm));
|
||||
timeout_check("connect");
|
||||
} while (ret < 0 && errno == EINTR);
|
||||
timeout_end();
|
||||
|
||||
if (ret < 0) {
|
||||
fd = vsock_stream_connect(opts->peer_cid, 1234);
|
||||
if (fd < 0) {
|
||||
perror("connect");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
@ -430,68 +399,21 @@ static void test_connect_client(unsigned int peer_cid)
|
||||
free_sock_stat(&sockets);
|
||||
}
|
||||
|
||||
static void test_connect_server(unsigned int peer_cid)
|
||||
static void test_connect_server(const struct test_opts *opts)
|
||||
{
|
||||
union {
|
||||
struct sockaddr sa;
|
||||
struct sockaddr_vm svm;
|
||||
} addr = {
|
||||
.svm = {
|
||||
.svm_family = AF_VSOCK,
|
||||
.svm_port = 1234,
|
||||
.svm_cid = VMADDR_CID_ANY,
|
||||
},
|
||||
};
|
||||
union {
|
||||
struct sockaddr sa;
|
||||
struct sockaddr_vm svm;
|
||||
} clientaddr;
|
||||
socklen_t clientaddr_len = sizeof(clientaddr.svm);
|
||||
LIST_HEAD(sockets);
|
||||
struct vsock_stat *st;
|
||||
int fd;
|
||||
LIST_HEAD(sockets);
|
||||
int client_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);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
client_fd = vsock_stream_accept(VMADDR_CID_ANY, 1234, NULL);
|
||||
if (client_fd < 0) {
|
||||
perror("accept");
|
||||
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 (clientaddr.svm.svm_cid != peer_cid) {
|
||||
fprintf(stderr, "expected peer CID %u from accept(2), got %u\n",
|
||||
peer_cid, clientaddr.svm.svm_cid);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
read_vsock_stat(&sockets);
|
||||
|
||||
check_num_sockets(&sockets, 2);
|
||||
find_vsock_stat(&sockets, fd);
|
||||
check_num_sockets(&sockets, 1);
|
||||
st = find_vsock_stat(&sockets, client_fd);
|
||||
check_socket_state(st, TCP_ESTABLISHED);
|
||||
|
||||
@ -499,15 +421,10 @@ static void test_connect_server(unsigned int peer_cid)
|
||||
control_expectln("DONE");
|
||||
|
||||
close(client_fd);
|
||||
close(fd);
|
||||
free_sock_stat(&sockets);
|
||||
}
|
||||
|
||||
static struct {
|
||||
const char *name;
|
||||
void (*run_client)(unsigned int peer_cid);
|
||||
void (*run_server)(unsigned int peer_cid);
|
||||
} test_cases[] = {
|
||||
static struct test_case test_cases[] = {
|
||||
{
|
||||
.name = "No sockets",
|
||||
.run_server = test_no_sockets,
|
||||
@ -524,30 +441,6 @@ static struct {
|
||||
{},
|
||||
};
|
||||
|
||||
static void init_signals(void)
|
||||
{
|
||||
struct sigaction act = {
|
||||
.sa_handler = sigalrm,
|
||||
};
|
||||
|
||||
sigaction(SIGALRM, &act, NULL);
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
}
|
||||
|
||||
static unsigned int parse_cid(const char *str)
|
||||
{
|
||||
char *endptr = NULL;
|
||||
unsigned long int n;
|
||||
|
||||
errno = 0;
|
||||
n = strtoul(str, &endptr, 10);
|
||||
if (errno || *endptr != '\0') {
|
||||
fprintf(stderr, "malformed CID \"%s\"\n", str);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
static const char optstring[] = "";
|
||||
static const struct option longopts[] = {
|
||||
{
|
||||
@ -570,6 +463,16 @@ static const struct option longopts[] = {
|
||||
.has_arg = required_argument,
|
||||
.val = 'p',
|
||||
},
|
||||
{
|
||||
.name = "list",
|
||||
.has_arg = no_argument,
|
||||
.val = 'l',
|
||||
},
|
||||
{
|
||||
.name = "skip",
|
||||
.has_arg = required_argument,
|
||||
.val = 's',
|
||||
},
|
||||
{
|
||||
.name = "help",
|
||||
.has_arg = no_argument,
|
||||
@ -580,7 +483,7 @@ static const struct option longopts[] = {
|
||||
|
||||
static void usage(void)
|
||||
{
|
||||
fprintf(stderr, "Usage: vsock_diag_test [--help] [--control-host=<host>] --control-port=<port> --mode=client|server --peer-cid=<cid>\n"
|
||||
fprintf(stderr, "Usage: vsock_diag_test [--help] [--control-host=<host>] --control-port=<port> --mode=client|server --peer-cid=<cid> [--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"
|
||||
@ -594,7 +497,18 @@ static void usage(void)
|
||||
"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");
|
||||
"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"
|
||||
" --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"
|
||||
);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
@ -602,9 +516,10 @@ int main(int argc, char **argv)
|
||||
{
|
||||
const char *control_host = NULL;
|
||||
const char *control_port = NULL;
|
||||
int mode = TEST_MODE_UNSET;
|
||||
unsigned int peer_cid = VMADDR_CID_ANY;
|
||||
int i;
|
||||
struct test_opts opts = {
|
||||
.mode = TEST_MODE_UNSET,
|
||||
.peer_cid = VMADDR_CID_ANY,
|
||||
};
|
||||
|
||||
init_signals();
|
||||
|
||||
@ -620,20 +535,27 @@ int main(int argc, char **argv)
|
||||
break;
|
||||
case 'm':
|
||||
if (strcmp(optarg, "client") == 0)
|
||||
mode = TEST_MODE_CLIENT;
|
||||
opts.mode = TEST_MODE_CLIENT;
|
||||
else if (strcmp(optarg, "server") == 0)
|
||||
mode = TEST_MODE_SERVER;
|
||||
opts.mode = TEST_MODE_SERVER;
|
||||
else {
|
||||
fprintf(stderr, "--mode must be \"client\" or \"server\"\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
break;
|
||||
case 'p':
|
||||
peer_cid = parse_cid(optarg);
|
||||
opts.peer_cid = parse_cid(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();
|
||||
@ -642,35 +564,21 @@ int main(int argc, char **argv)
|
||||
|
||||
if (!control_port)
|
||||
usage();
|
||||
if (mode == TEST_MODE_UNSET)
|
||||
if (opts.mode == TEST_MODE_UNSET)
|
||||
usage();
|
||||
if (peer_cid == VMADDR_CID_ANY)
|
||||
if (opts.peer_cid == VMADDR_CID_ANY)
|
||||
usage();
|
||||
|
||||
if (!control_host) {
|
||||
if (mode != TEST_MODE_SERVER)
|
||||
if (opts.mode != TEST_MODE_SERVER)
|
||||
usage();
|
||||
control_host = "0.0.0.0";
|
||||
}
|
||||
|
||||
control_init(control_host, control_port, mode == TEST_MODE_SERVER);
|
||||
control_init(control_host, control_port,
|
||||
opts.mode == TEST_MODE_SERVER);
|
||||
|
||||
for (i = 0; test_cases[i].name; i++) {
|
||||
void (*run)(unsigned int peer_cid);
|
||||
|
||||
printf("%s...", test_cases[i].name);
|
||||
fflush(stdout);
|
||||
|
||||
if (mode == TEST_MODE_CLIENT)
|
||||
run = test_cases[i].run_client;
|
||||
else
|
||||
run = test_cases[i].run_server;
|
||||
|
||||
if (run)
|
||||
run(peer_cid);
|
||||
|
||||
printf("ok\n");
|
||||
}
|
||||
run_tests(test_cases, &opts);
|
||||
|
||||
control_cleanup();
|
||||
return EXIT_SUCCESS;
|
||||
|
379
tools/testing/vsock/vsock_test.c
Normal file
379
tools/testing/vsock/vsock_test.c
Normal file
@ -0,0 +1,379 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* vsock_test - vsock.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 <linux/kernel.h>
|
||||
|
||||
#include "timeout.h"
|
||||
#include "control.h"
|
||||
#include "util.h"
|
||||
|
||||
static void test_stream_connection_reset(const struct test_opts *opts)
|
||||
{
|
||||
union {
|
||||
struct sockaddr sa;
|
||||
struct sockaddr_vm svm;
|
||||
} addr = {
|
||||
.svm = {
|
||||
.svm_family = AF_VSOCK,
|
||||
.svm_port = 1234,
|
||||
.svm_cid = opts->peer_cid,
|
||||
},
|
||||
};
|
||||
int ret;
|
||||
int fd;
|
||||
|
||||
fd = socket(AF_VSOCK, SOCK_STREAM, 0);
|
||||
|
||||
timeout_begin(TIMEOUT);
|
||||
do {
|
||||
ret = connect(fd, &addr.sa, sizeof(addr.svm));
|
||||
timeout_check("connect");
|
||||
} while (ret < 0 && errno == EINTR);
|
||||
timeout_end();
|
||||
|
||||
if (ret != -1) {
|
||||
fprintf(stderr, "expected connect(2) failure, got %d\n", ret);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (errno != ECONNRESET) {
|
||||
fprintf(stderr, "unexpected connect(2) errno %d\n", errno);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void test_stream_client_close_client(const struct test_opts *opts)
|
||||
{
|
||||
int fd;
|
||||
|
||||
fd = vsock_stream_connect(opts->peer_cid, 1234);
|
||||
if (fd < 0) {
|
||||
perror("connect");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
send_byte(fd, 1, 0);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void test_stream_client_close_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);
|
||||
}
|
||||
|
||||
/* Wait for the remote to close the connection, before check
|
||||
* -EPIPE error on send.
|
||||
*/
|
||||
vsock_wait_remote_close(fd);
|
||||
|
||||
send_byte(fd, -EPIPE, 0);
|
||||
recv_byte(fd, 1, 0);
|
||||
recv_byte(fd, 0, 0);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void test_stream_server_close_client(const struct test_opts *opts)
|
||||
{
|
||||
int fd;
|
||||
|
||||
fd = vsock_stream_connect(opts->peer_cid, 1234);
|
||||
if (fd < 0) {
|
||||
perror("connect");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Wait for the remote to close the connection, before check
|
||||
* -EPIPE error on send.
|
||||
*/
|
||||
vsock_wait_remote_close(fd);
|
||||
|
||||
send_byte(fd, -EPIPE, 0);
|
||||
recv_byte(fd, 1, 0);
|
||||
recv_byte(fd, 0, 0);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void test_stream_server_close_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);
|
||||
}
|
||||
|
||||
send_byte(fd, 1, 0);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
/* With the standard socket sizes, VMCI is able to support about 100
|
||||
* concurrent stream connections.
|
||||
*/
|
||||
#define MULTICONN_NFDS 100
|
||||
|
||||
static void test_stream_multiconn_client(const struct test_opts *opts)
|
||||
{
|
||||
int fds[MULTICONN_NFDS];
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MULTICONN_NFDS; i++) {
|
||||
fds[i] = vsock_stream_connect(opts->peer_cid, 1234);
|
||||
if (fds[i] < 0) {
|
||||
perror("connect");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < MULTICONN_NFDS; i++) {
|
||||
if (i % 2)
|
||||
recv_byte(fds[i], 1, 0);
|
||||
else
|
||||
send_byte(fds[i], 1, 0);
|
||||
}
|
||||
|
||||
for (i = 0; i < MULTICONN_NFDS; i++)
|
||||
close(fds[i]);
|
||||
}
|
||||
|
||||
static void test_stream_multiconn_server(const struct test_opts *opts)
|
||||
{
|
||||
int fds[MULTICONN_NFDS];
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MULTICONN_NFDS; i++) {
|
||||
fds[i] = vsock_stream_accept(VMADDR_CID_ANY, 1234, NULL);
|
||||
if (fds[i] < 0) {
|
||||
perror("accept");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < MULTICONN_NFDS; i++) {
|
||||
if (i % 2)
|
||||
send_byte(fds[i], 1, 0);
|
||||
else
|
||||
recv_byte(fds[i], 1, 0);
|
||||
}
|
||||
|
||||
for (i = 0; i < MULTICONN_NFDS; i++)
|
||||
close(fds[i]);
|
||||
}
|
||||
|
||||
static void test_stream_msg_peek_client(const struct test_opts *opts)
|
||||
{
|
||||
int fd;
|
||||
|
||||
fd = vsock_stream_connect(opts->peer_cid, 1234);
|
||||
if (fd < 0) {
|
||||
perror("connect");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
send_byte(fd, 1, 0);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void test_stream_msg_peek_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);
|
||||
}
|
||||
|
||||
recv_byte(fd, 1, MSG_PEEK);
|
||||
recv_byte(fd, 1, 0);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static struct test_case test_cases[] = {
|
||||
{
|
||||
.name = "SOCK_STREAM connection reset",
|
||||
.run_client = test_stream_connection_reset,
|
||||
},
|
||||
{
|
||||
.name = "SOCK_STREAM client close",
|
||||
.run_client = test_stream_client_close_client,
|
||||
.run_server = test_stream_client_close_server,
|
||||
},
|
||||
{
|
||||
.name = "SOCK_STREAM server close",
|
||||
.run_client = test_stream_server_close_client,
|
||||
.run_server = test_stream_server_close_server,
|
||||
},
|
||||
{
|
||||
.name = "SOCK_STREAM multiple connections",
|
||||
.run_client = test_stream_multiconn_client,
|
||||
.run_server = test_stream_multiconn_server,
|
||||
},
|
||||
{
|
||||
.name = "SOCK_STREAM MSG_PEEK",
|
||||
.run_client = test_stream_msg_peek_client,
|
||||
.run_server = test_stream_msg_peek_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 = "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_test [--help] [--control-host=<host>] --control-port=<port> --mode=client|server --peer-cid=<cid> [--list] [--skip=<test_id>]\n"
|
||||
"\n"
|
||||
" Server: vsock_test --control-port=1234 --mode=server --peer-cid=3\n"
|
||||
" Client: vsock_test --control-host=192.168.0.1 --control-port=1234 --mode=client --peer-cid=2\n"
|
||||
"\n"
|
||||
"Run vsock.ko tests. Must be launched in both guest\n"
|
||||
"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"
|
||||
" --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"
|
||||
);
|
||||
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,
|
||||
};
|
||||
|
||||
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 '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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user