mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Sergio Lopez <slp@redhat.com>
To: "Stefano Garzarella" <sgarzare@redhat.com>,
	"Bobby Eshleman" <bobbyeshleman@meta.com>,
	"David S. Miller" <davem@davemloft.net>,
	"Eric Dumazet" <edumazet@kernel.org>,
	"Jakub Kicinski" <kuba@kernel.org>,
	"Paolo Abeni" <pabeni@redhat.com>,
	"Simon Horman" <horms@kernel.org>,
	"Bryan Tan" <bryan-bt.tan@broadcom.com>,
	"Vishnu Dasa" <vishnu.dasa@broadcom.com>,
	"Broadcom internal kernel review list"
	<bcm-kernel-feedback-list@broadcom.com>,
	"Stefan Hajnoczi" <stefanha@redhat.com>,
	"Michael S. Tsirkin" <mst@redhat.com>,
	"Jason Wang" <jasowangio@gmail.com>,
	"Eugenio Pérez" <eperezma@redhat.com>,
	"Xuan Zhuo" <xuanzhuo@linux.alibaba.com>,
	"Haiyang Zhang" <haiyangz@microsoft.com>,
	"Wei Liu" <wei.liu@kernel.org>,
	"Dexuan Cui" <decui@microsoft.com>
Cc: virtualization@lists.linux.dev, netdev@vger.kernel.org,
	 linux-kernel@vger.kernel.org, kvm@vger.kernel.org,
	bpf@vger.kernel.org,  linux-hyperv@vger.kernel.org,
	Sergio Lopez <slp@redhat.com>
Subject: [PATCH RFC 8/8] vsock: add datagram tests to vsock_test
Date: Wed, 28 Oct 2026 17:04:37 +0100	[thread overview]
Message-ID: <20261028-vsock-dgram-support-v1-8-e457cf7cee78@sinrega.org> (raw)
In-Reply-To: <20261028-vsock-dgram-support-v1-0-e457cf7cee78@sinrega.org>

Cover local semantics, host/guest transfers, routing and queue
accounting. Synchronize capability-based skips and document manual
checks.

Signed-off-by: Sergio Lopez <slp@redhat.com>
---
 tools/testing/vsock/Makefile           |   3 +-
 tools/testing/vsock/README             |  23 +-
 tools/testing/vsock/util.c             |  62 +--
 tools/testing/vsock/util.h             |  10 +
 tools/testing/vsock/vsock_test.c       | 154 ++++++-
 tools/testing/vsock/vsock_test_dgram.c | 809 +++++++++++++++++++++++++++++++++
 tools/testing/vsock/vsock_test_dgram.h |  26 ++
 7 files changed, 1055 insertions(+), 32 deletions(-)

diff --git a/tools/testing/vsock/Makefile b/tools/testing/vsock/Makefile
index 88211fd132d2..78e2cb7b1193 100644
--- a/tools/testing/vsock/Makefile
+++ b/tools/testing/vsock/Makefile
@@ -1,7 +1,8 @@
 # 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_test: vsock_test.o vsock_test_zerocopy.o vsock_test_dgram.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
 
diff --git a/tools/testing/vsock/README b/tools/testing/vsock/README
index 680ce666ceb5..be34b032ef13 100644
--- a/tools/testing/vsock/README
+++ b/tools/testing/vsock/README
@@ -5,7 +5,7 @@ Hyper-V.
 
 The following tests are available:
 
-  * vsock_test - core AF_VSOCK socket functionality
+  * vsock_test - stream, seqpacket and datagram functionality
   * vsock_diag_test - vsock_diag.ko module for listing open sockets
 
 The following prerequisite steps are not automated and must be performed prior
@@ -32,7 +32,7 @@ Invoke test binaries in both directions as follows:
                         --control-port=1234 \
                         --peer-cid=2
   (host)# $TEST_BINARY --mode=client \
-                       --control-port=$GUEST_IP \
+                       --control-host=$GUEST_IP \
                        --control-port=1234 \
                        --peer-cid=3
 
@@ -51,6 +51,25 @@ suite run, e.g.
 
 For more information see Documentation/dev-tools/kmemleak.rst.
 
+Datagram tests
+--------------
+vsock_test includes datagram cases in the client/server runs above. Unsupported
+cases print a skip reason.
+
+Virtio/vhost datagrams require kernel support on both sides and a VMM that
+negotiates VIRTIO_VSOCK_F_DGRAM. Load vsock_loopback on both host and guest for
+the local cases, and make /dev/vsock available. VMCI's dedicated datagram
+transport takes priority when loaded, causing virtio/vhost cases to skip.
+
+Run only the local datagram cases without a peer or TCP control connection:
+
+  ./vsock_test --local
+
+Use --list and --pick <test_id> to select individual cases. The BPF admission
+test (48, requiring BPF privileges) and nested routing test (49, requiring both
+virtio and vhost transports) need explicit selection. The VMCI queue-length
+test (53) runs only with VMCI.
+
 vsock_perf utility
 -------------------
 'vsock_perf' is a simple tool to measure vsock performance. It works in
diff --git a/tools/testing/vsock/util.c b/tools/testing/vsock/util.c
index 299a4e8ecb63..6ad97403697f 100644
--- a/tools/testing/vsock/util.c
+++ b/tools/testing/vsock/util.c
@@ -499,47 +499,55 @@ void run_tests(const struct test_case *test_cases,
 	int i;
 
 	for (i = 0; test_cases[i].name; i++) {
+		const struct test_case *test = &test_cases[i];
 		void (*run)(const struct test_opts *opts);
+		const char *reason;
 		char *line;
 
-		printf("%d - %s...", i, test_cases[i].name);
+		if (opts->local && !test->local)
+			continue;
+		printf("%d - %s...", i, test->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");
-
+		if (opts->local) {
+			if (test->skip) {
+				printf("skipped\n");
+				continue;
+			}
+		} else {
+			/* Agree on explicit selection before any capability check
+			 * or test can exchange messages on the control connection.
+			 */
+			control_writeln(test->skip ? "SKIP" : "NEXT");
+			line = control_readln();
+			if (control_cmpln(line, "SKIP", false) || test->skip) {
+				printf("skipped\n");
+				free(line);
+				continue;
+			}
+			control_cmpln(line, "NEXT", true);
 			free(line);
-			continue;
 		}
 
-		control_cmpln(line, "NEXT", true);
-		free(line);
-
-		if (opts->mode == TEST_MODE_CLIENT)
-			run = test_cases[i].run_client;
+		if (test->check) {
+			reason = test->check(opts);
+			if (reason) {
+				printf("skipped: %s\n", reason);
+				continue;
+			}
+		}
+		if (opts->local || opts->mode == TEST_MODE_CLIENT)
+			run = test->run_client;
 		else
-			run = test_cases[i].run_server;
-
+			run = test->run_server;
 		if (run)
 			run(opts);
-
 		printf("ok\n");
 	}
 
+	if (opts->local)
+		return;
+
 	printf("All tests have been executed. Waiting other peer...");
 	fflush(stdout);
 
diff --git a/tools/testing/vsock/util.h b/tools/testing/vsock/util.h
index 9dabb547021e..e23290e7dcc6 100644
--- a/tools/testing/vsock/util.h
+++ b/tools/testing/vsock/util.h
@@ -51,6 +51,10 @@ struct test_opts {
 	enum test_mode mode;
 	unsigned int peer_cid;
 	unsigned int peer_port;
+	unsigned int bind_cid;
+	bool local;
+	bool to_host;
+	bool expect_to_host;
 };
 
 /* A test case definition.  Test functions must print failures to stderr and
@@ -66,6 +70,12 @@ struct test_case {
 	void (*run_server)(const struct test_opts *opts);
 
 	bool skip;
+	bool local; /* Can run without a control connection */
+
+	/* Return a skip reason or NULL after the selection barrier. Checks
+	 * that coordinate over the control channel must agree on both peers.
+	 */
+	const char *(*check)(const struct test_opts *opts);
 };
 
 void init_signals(void);
diff --git a/tools/testing/vsock/vsock_test.c b/tools/testing/vsock/vsock_test.c
index 10b9074fb87f..f4ff6a42a9cb 100644
--- a/tools/testing/vsock/vsock_test.c
+++ b/tools/testing/vsock/vsock_test.c
@@ -27,6 +27,7 @@
 #include <linux/sockios.h>
 
 #include "vsock_test_zerocopy.h"
+#include "vsock_test_dgram.h"
 #include "timeout.h"
 #include "control.h"
 #include "util.h"
@@ -2911,6 +2912,95 @@ static struct test_case test_cases[] = {
 	{
 		.name = "SO_INQ option validation",
 		.run_client = test_so_inq_validation_client,
+	},
+		.name = "SOCK_DGRAM autobind, duplicate bind and wildcard destination",
+		.run_client = test_dgram_bind,
+		.run_server = test_dgram_bind,
+		.check = vsock_dgram_check_local,
+		.local = true,
+	},
+	{
+		.name = "SOCK_DGRAM queue ioctls, poll, peek, truncation and empty messages",
+		.run_client = test_dgram_receive,
+		.run_server = test_dgram_receive,
+		.check = vsock_dgram_check_local,
+		.local = true,
+	},
+	{
+		.name = "SOCK_DGRAM unsupported receive flags preserve normal data",
+		.run_client = test_dgram_receive_flags,
+		.run_server = test_dgram_receive_flags,
+		.check = vsock_dgram_check_local,
+		.local = true,
+	},
+	{
+		.name = "SOCK_DGRAM socket filter trims receive length",
+		.run_client = test_dgram_filter,
+		.run_server = test_dgram_filter,
+		.check = vsock_dgram_check_local,
+		.local = true,
+	},
+	{
+		.name = "SOCK_DGRAM connect, disconnect and shutdown peer filtering",
+		.run_client = test_dgram_connect,
+		.run_server = test_dgram_connect,
+		.check = vsock_dgram_check_local,
+		.local = true,
+	},
+	{
+		.name = "SOCK_DGRAM explicit loopback source CIDs and reply addresses",
+		.run_client = test_dgram_source_cid,
+		.run_server = test_dgram_source_cid,
+		.check = vsock_dgram_check_local,
+		.local = true,
+	},
+	{
+		.name = "SOCK_DGRAM connected stream/seqpacket cannot shadow datagrams",
+		.run_client = test_dgram_bound_lookup,
+		.run_server = test_dgram_bound_lookup,
+		.check = vsock_dgram_check_local,
+		.local = true,
+	},
+	{
+		.name = "SOCK_DGRAM sockmap and sockhash reject datagrams (BPF privileges)",
+		.run_client = test_dgram_sockmap,
+		.run_server = test_dgram_sockmap,
+		.skip = true,
+		.check = vsock_dgram_check_local,
+		.local = true,
+	},
+	{
+		.name = "SOCK_DGRAM nested CID_HOST loopback rejection (nested VM only)",
+		.run_client = test_dgram_nested_source,
+		.run_server = test_dgram_nested_source,
+		.skip = true,
+		.check = vsock_dgram_check_local,
+		.local = true,
+	},
+	{
+		.name = "SOCK_DGRAM bidirectional sizes, 64 KiB boundaries and RX refill",
+		.run_client = test_dgram_sizes,
+		.run_server = test_dgram_sizes,
+		.check = vsock_dgram_check_peer,
+	},
+	{
+		.name = "SOCK_DGRAM connected socket alternates loopback and peer routes",
+		.run_client = test_dgram_routes,
+		.run_server = test_dgram_routes,
+		.check = vsock_dgram_check_routes,
+	},
+	{
+		.name = "SOCK_DGRAM peer filtering and disconnect across transports",
+		.run_client = test_dgram_peer_filter,
+		.run_server = test_dgram_peer_filter,
+		.check = vsock_dgram_check_peer,
+	},
+	{
+		.name = "SOCK_DGRAM VMCI ioctl(SIOCINQ) payload length",
+		.run_client = test_dgram_vmci_inq,
+		.run_server = test_dgram_vmci_inq,
+		.check = vsock_dgram_check_vmci,
+		.local = true,
 	},
 	{},
 };
@@ -2957,6 +3047,26 @@ static const struct option longopts[] = {
 		.has_arg = required_argument,
 		.val = 't',
 	},
+	{
+		.name = "local",
+		.has_arg = no_argument,
+		.val = 'L',
+	},
+	{
+		.name = "bind-cid",
+		.has_arg = required_argument,
+		.val = 'b',
+	},
+	{
+		.name = "to-host",
+		.has_arg = no_argument,
+		.val = 'T',
+	},
+	{
+		.name = "expect-to-host",
+		.has_arg = no_argument,
+		.val = 'E',
+	},
 	{
 		.name = "help",
 		.has_arg = no_argument,
@@ -2972,8 +3082,10 @@ static void usage(void)
 		"  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"
+		"  Local: vsock_test --local [--pick=<test_id>]\n"
+		"\n"
+		"Run vsock.ko tests. Paired 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"
@@ -2986,8 +3098,14 @@ static void usage(void)
 		"specified with --peer-port=<port> (or the default port)\n"
 		"and the next one.\n"
 		"\n"
+		"--local runs local datagram cases without a control connection.\n"
+		"\n"
 		"Options:\n"
 		"  --help                 This help message\n"
+		"  --local                Run local datagram cases without a peer\n"
+		"  --bind-cid <cid>       Source CID for peer datagram tests [default: ANY]\n"
+		"  --to-host              Route peer datagrams through the frontend\n"
+		"  --expect-to-host       Expect the upstream datagram source flag\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"
@@ -3011,7 +3129,10 @@ int main(int argc, char **argv)
 		.mode = TEST_MODE_UNSET,
 		.peer_cid = VMADDR_CID_ANY,
 		.peer_port = DEFAULT_PEER_PORT,
+		.bind_cid = VMADDR_CID_ANY,
 	};
+	bool picked = false;
+	size_t i;
 
 	srand(time(NULL));
 	init_signals();
@@ -3023,6 +3144,18 @@ int main(int argc, char **argv)
 			break;
 
 		switch (opt) {
+		case 'L':
+			opts.local = true;
+			break;
+		case 'b':
+			opts.bind_cid = parse_cid(optarg);
+			break;
+		case 'T':
+			opts.to_host = true;
+			break;
+		case 'E':
+			opts.expect_to_host = true;
+			break;
 		case 'H':
 			control_host = optarg;
 			break;
@@ -3055,6 +3188,7 @@ int main(int argc, char **argv)
 		case 't':
 			pick_test(test_cases, ARRAY_SIZE(test_cases) - 1,
 				  optarg);
+			picked = true;
 			break;
 		case '?':
 		default:
@@ -3062,6 +3196,22 @@ int main(int argc, char **argv)
 		}
 	}
 
+	if (optind != argc)
+		usage();
+	if (opts.local) {
+		if (opts.mode != TEST_MODE_UNSET || control_host || control_port)
+			usage();
+		if (picked) {
+			for (i = 0; test_cases[i].name; i++) {
+				if (!test_cases[i].skip && !test_cases[i].local) {
+					fprintf(stderr, "test %zu requires paired mode\n", i);
+					exit(EXIT_FAILURE);
+				}
+			}
+		}
+		run_tests(test_cases, &opts);
+		return EXIT_SUCCESS;
+	}
 	if (!control_port)
 		usage();
 	if (opts.mode == TEST_MODE_UNSET)
diff --git a/tools/testing/vsock/vsock_test_dgram.c b/tools/testing/vsock/vsock_test_dgram.c
new file mode 100644
index 000000000000..d818f43176dc
--- /dev/null
+++ b/tools/testing/vsock/vsock_test_dgram.c
@@ -0,0 +1,809 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Datagram feature tests for vsock. */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/bpf.h>
+#include <linux/filter.h>
+#include <linux/sockios.h>
+#include <poll.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/syscall.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "control.h"
+#include "timeout.h"
+#include "vsock_test_dgram.h"
+
+#define MAX_DGRAM (64 * 1024)
+
+static void fail(const char *fmt, ...)
+{
+	va_list ap;
+
+	fprintf(stderr, "\nFAIL: ");
+	va_start(ap, fmt);
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+	fputc('\n', stderr);
+	exit(EXIT_FAILURE);
+}
+
+static void check(int ret, const char *operation)
+{
+	if (ret < 0)
+		fail("%s: %s", operation, strerror(errno));
+}
+
+static void expect_error(ssize_t ret, int error, const char *operation)
+{
+	if (ret != -1 || errno != error)
+		fail("%s: expected %s, got %zd (%s)", operation,
+		     strerror(error), ret, ret == -1 ? strerror(errno) : "success");
+}
+
+static int64_t now_ms(void)
+{
+	struct timespec ts;
+
+	check(clock_gettime(CLOCK_MONOTONIC, &ts), "clock_gettime");
+	return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
+}
+
+static short poll_fd(int fd, short events, int milliseconds)
+{
+	struct pollfd pfd = { .fd = fd, .events = events };
+	int ret;
+
+	do {
+		ret = poll(&pfd, 1, milliseconds);
+	} while (ret < 0 && errno == EINTR);
+	check(ret, "poll");
+	if (pfd.revents & (POLLERR | POLLNVAL))
+		fail("unexpected poll events %#x", pfd.revents);
+	return pfd.revents;
+}
+
+static void wait_readable(int fd)
+{
+	if (!(poll_fd(fd, POLLIN, TIMEOUT * 1000) & POLLIN))
+		fail("waiting for socket readability timed out");
+}
+
+static struct sockaddr_vm address(unsigned int cid, unsigned int port)
+{
+	return (struct sockaddr_vm) {
+		.svm_family = AF_VSOCK,
+		.svm_cid = cid,
+		.svm_port = port,
+	};
+}
+
+static struct sockaddr_vm sock_name(int fd, bool peer)
+{
+	struct sockaddr_vm addr;
+	socklen_t len = sizeof(addr);
+	int ret;
+
+	if (peer)
+		ret = getpeername(fd, (struct sockaddr *)&addr, &len);
+	else
+		ret = getsockname(fd, (struct sockaddr *)&addr, &len);
+	check(ret, peer ? "getpeername" : "getsockname");
+	if (len != sizeof(addr) || addr.svm_family != AF_VSOCK)
+		fail("invalid socket address");
+	return addr;
+}
+
+static unsigned int local_cid(void)
+{
+	unsigned int cid;
+	int fd;
+
+	fd = open("/dev/vsock", O_RDONLY | O_CLOEXEC);
+	check(fd, "open /dev/vsock");
+	check(ioctl(fd, IOCTL_VM_SOCKETS_GET_LOCAL_CID, &cid), "get local CID");
+	close(fd);
+	if (cid == VMADDR_CID_ANY || cid <= VMADDR_CID_LOCAL)
+		fail("a virtio or vhost transport is required (local CID %u)", cid);
+	return cid;
+}
+
+static void bind_fd(int fd, unsigned int cid, unsigned int port)
+{
+	struct sockaddr_vm addr = address(cid, port);
+
+	check(bind(fd, (struct sockaddr *)&addr, sizeof(addr)), "bind");
+}
+
+static int dgram_socket(void)
+{
+	int fd = socket(AF_VSOCK, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
+	int rcvbuf = 1024 * 1024;
+
+	check(fd, "socket(AF_VSOCK, SOCK_DGRAM): check transport/feature support");
+	check(setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)), "SO_RCVBUF");
+	return fd;
+}
+
+enum dgram_capability {
+	DGRAM_SOCKET = 1 << 0,
+	DGRAM_VIRTIO = 1 << 1,
+	DGRAM_VMCI = 1 << 2,
+	DGRAM_LOOPBACK = 1 << 3,
+	DGRAM_PEER = 1 << 4,
+};
+
+static bool dgram_unavailable(int error)
+{
+	return error == EAFNOSUPPORT || error == EPROTONOSUPPORT ||
+	       error == ESOCKTNOSUPPORT || error == ENODEV;
+}
+
+static unsigned int dgram_capabilities(const struct test_opts *opts,
+				       unsigned int needs)
+{
+	unsigned int caps = DGRAM_SOCKET;
+	struct sockaddr_vm peer;
+	int fd, local_fd;
+
+	fd = socket(AF_VSOCK, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
+	if (fd < 0) {
+		if (dgram_unavailable(errno))
+			return 0;
+		check(fd, "probe datagram socket");
+	}
+	/* VMCI registers the dedicated datagram transport when loaded. */
+	if (!access("/sys/module/vmw_vsock_vmci_transport", F_OK))
+		caps |= DGRAM_VMCI;
+	else
+		caps |= DGRAM_VIRTIO;
+
+	if ((needs & DGRAM_PEER) && (caps & DGRAM_VIRTIO)) {
+		bind_fd(fd, opts->bind_cid, VMADDR_PORT_ANY);
+		peer = address(opts->peer_cid, opts->peer_port);
+		if (opts->to_host)
+			peer.svm_flags = VMADDR_FLAG_TO_HOST;
+		if (!connect(fd, (struct sockaddr *)&peer, sizeof(peer)))
+			caps |= DGRAM_PEER;
+		else if (!dgram_unavailable(errno) && errno != EINVAL)
+			check(-1, "probe peer datagram route");
+	}
+	close(fd);
+
+	if ((needs & DGRAM_LOOPBACK) && (caps & DGRAM_VIRTIO)) {
+		/* Binding a stream socket to CID_LOCAL probes registration of
+		 * the loopback transport without sending a packet to the host.
+		 */
+		local_fd = vsock_bind_try(VMADDR_CID_LOCAL, VMADDR_PORT_ANY, SOCK_STREAM);
+		if (local_fd >= 0) {
+			caps |= DGRAM_LOOPBACK;
+			close(local_fd);
+		} else if (errno != EADDRNOTAVAIL) {
+			check(local_fd, "probe loopback transport");
+		}
+	}
+	return caps;
+}
+
+static const char *dgram_check(const struct test_opts *opts, unsigned int needs)
+{
+	unsigned int local = dgram_capabilities(opts, needs);
+	unsigned int both = local;
+
+	/* run_tests() has already synchronized explicit --pick/--skip
+	 * selection. Both peers can now safely exchange capabilities.
+	 */
+	if (!opts->local) {
+		control_writeulong(local);
+		both &= control_readulong();
+	}
+	if (!(both & DGRAM_SOCKET))
+		return local & DGRAM_SOCKET ?
+			"datagrams unavailable at peer" : "datagrams unavailable locally";
+	if ((needs & DGRAM_VIRTIO) && !(both & DGRAM_VIRTIO))
+		return "requires virtio/vhost datagram transports on both peers";
+	if ((needs & DGRAM_VMCI) && !(both & DGRAM_VMCI))
+		return "requires VMCI datagram transports on both peers";
+	if ((needs & DGRAM_LOOPBACK) && !(both & DGRAM_LOOPBACK))
+		return "load vsock_loopback on both peers";
+	if ((needs & DGRAM_PEER) && !(both & DGRAM_PEER))
+		return "peer route does not support datagrams";
+	return NULL;
+}
+
+const char *vsock_dgram_check_local(const struct test_opts *opts)
+{
+	return dgram_check(opts, DGRAM_VIRTIO | DGRAM_LOOPBACK);
+}
+
+const char *vsock_dgram_check_peer(const struct test_opts *opts)
+{
+	return dgram_check(opts, DGRAM_VIRTIO | DGRAM_PEER);
+}
+
+const char *vsock_dgram_check_routes(const struct test_opts *opts)
+{
+	return dgram_check(opts, DGRAM_VIRTIO | DGRAM_PEER | DGRAM_LOOPBACK);
+}
+
+const char *vsock_dgram_check_vmci(const struct test_opts *opts)
+{
+	return dgram_check(opts, DGRAM_VMCI);
+}
+
+static int bound_socket(unsigned int cid)
+{
+	int fd = dgram_socket();
+
+	bind_fd(fd, cid, VMADDR_PORT_ANY);
+	return fd;
+}
+
+static struct sockaddr_vm local_address(int fd)
+{
+	struct sockaddr_vm addr = sock_name(fd, false);
+
+	addr.svm_cid = VMADDR_CID_LOCAL;
+	return addr;
+}
+
+static void connect_fd(int fd, const struct sockaddr_vm *addr)
+{
+	check(connect(fd, (const struct sockaddr *)addr, sizeof(*addr)), "connect");
+}
+
+static void disconnect_fd(int fd)
+{
+	struct sockaddr_vm addr = { .svm_family = AF_UNSPEC };
+	socklen_t len = sizeof(addr);
+
+	check(connect(fd, (struct sockaddr *)&addr, sizeof(addr)), "disconnect");
+	expect_error(getpeername(fd, (struct sockaddr *)&addr, &len),
+		     ENOTCONN, "getpeername after disconnect");
+}
+
+static int queue_bytes(int fd, unsigned long op)
+{
+	int bytes;
+
+	check(ioctl(fd, op, &bytes), "queue ioctl");
+	if (bytes < 0)
+		fail("negative queue length: %d", bytes);
+	return bytes;
+}
+
+static void expect_inq(int fd, int expected)
+{
+	int actual = queue_bytes(fd, SIOCINQ);
+
+	if (actual != expected)
+		fail("SIOCINQ: expected %d, got %d", expected, actual);
+}
+
+static void wait_sent(int fd)
+{
+	int64_t deadline = now_ms() + TIMEOUT * 1000;
+
+	while (queue_bytes(fd, SIOCOUTQ)) {
+		if (now_ms() >= deadline)
+			fail("SIOCOUTQ did not return to zero");
+		usleep(1000);
+	}
+}
+
+static void send_packet(int fd, const struct sockaddr_vm *addr,
+			const void *buf, size_t len)
+{
+	int64_t deadline = now_ms() + TIMEOUT * 1000;
+	ssize_t ret;
+
+	for (;;) {
+		ret = sendto(fd, buf, len, MSG_DONTWAIT | MSG_NOSIGNAL,
+			     (const struct sockaddr *)addr, addr ? sizeof(*addr) : 0);
+		if (ret >= 0)
+			break;
+		if (errno != EAGAIN && errno != EINTR)
+			fail("sendto(%zu bytes): %s", len, strerror(errno));
+		if (now_ms() >= deadline)
+			fail("sendto(%zu bytes) timed out", len);
+		poll_fd(fd, POLLOUT, 1);
+	}
+	if (ret != len)
+		fail("partial datagram send: %zd/%zu", ret, len);
+}
+
+static struct sockaddr_vm recv_packet(int fd, void *buf, size_t capacity,
+				      int flags, size_t expected, bool truncated)
+{
+	struct sockaddr_vm src = { 0 };
+	struct iovec iov = { .iov_base = buf, .iov_len = capacity };
+	struct msghdr msg = {
+		.msg_name = &src,
+		.msg_namelen = sizeof(src),
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+	};
+	ssize_t ret;
+
+	wait_readable(fd);
+	ret = recvmsg(fd, &msg, flags | MSG_DONTWAIT);
+	check(ret, "recvmsg");
+	if (ret != expected || !!(msg.msg_flags & MSG_TRUNC) != truncated)
+		fail("recvmsg: expected %zu bytes, trunc=%d; got %zd, flags=%#x",
+		     expected, truncated, ret, msg.msg_flags);
+	if (msg.msg_namelen != sizeof(src) || src.svm_family != AF_VSOCK)
+		fail("invalid recvmsg source address");
+	return src;
+}
+
+static void expect_empty(int fd)
+{
+	char byte;
+
+	if (poll_fd(fd, POLLIN, 0) & POLLIN)
+		fail("unexpected queued datagram");
+	expect_error(recv(fd, &byte, 1, MSG_DONTWAIT), EAGAIN, "empty recv");
+	expect_inq(fd, 0);
+}
+
+void test_dgram_bind(const struct test_opts *opts)
+{
+	int rx = bound_socket(VMADDR_CID_ANY);
+	struct sockaddr_vm dest = local_address(rx);
+	int tx = dgram_socket();
+	int other = dgram_socket();
+	struct sockaddr_vm src;
+	char byte;
+
+	dest.svm_cid = VMADDR_CID_ANY;
+	expect_error(bind(other, (struct sockaddr *)&dest, sizeof(dest)),
+		     EADDRINUSE, "duplicate bind");
+	send_packet(tx, &dest, "A", 1);
+	src = recv_packet(rx, &byte, 1, 0, 1, false);
+	if (byte != 'A' || src.svm_port != sock_name(tx, false).svm_port ||
+	    src.svm_cid != VMADDR_CID_LOCAL || src.svm_port < 1024)
+		fail("autobind or wildcard destination returned an invalid source");
+	close(rx);
+	bind_fd(other, VMADDR_CID_ANY, dest.svm_port);
+	close(other);
+	close(tx);
+}
+
+void test_dgram_receive(const struct test_opts *opts)
+{
+	int rx = bound_socket(VMADDR_CID_ANY);
+	int tx = bound_socket(VMADDR_CID_ANY);
+	struct sockaddr_vm dest = local_address(rx);
+	char buf[80];
+	char first[31], second[79];
+
+	memset(first, 'A', sizeof(first));
+	memset(second, 'B', sizeof(second));
+	expect_empty(rx);
+	if (!(poll_fd(tx, POLLOUT, 0) & POLLOUT))
+		fail("new socket is not writable");
+	if (queue_bytes(tx, SIOCOUTQ))
+		fail("new socket has pending output");
+	send_packet(tx, &dest, first, sizeof(first));
+	send_packet(tx, &dest, second, sizeof(second));
+	wait_readable(rx);
+	expect_inq(rx, sizeof(first));
+	recv_packet(rx, buf, 5, MSG_PEEK, 5, true);
+	if (memcmp(buf, first, 5))
+		fail("MSG_PEEK payload mismatch");
+	expect_inq(rx, sizeof(first));
+	recv_packet(rx, buf, 5, MSG_TRUNC, sizeof(first), true);
+	wait_readable(rx);
+	expect_inq(rx, sizeof(second));
+	recv_packet(rx, buf, sizeof(buf), 0, sizeof(second), false);
+	if (memcmp(buf, second, sizeof(second)))
+		fail("datagram boundary or payload mismatch");
+	send_packet(tx, &dest, "", 0);
+	wait_readable(rx);
+	expect_inq(rx, 0);
+	recv_packet(rx, buf, sizeof(buf), MSG_PEEK, 0, false);
+	recv_packet(rx, buf, sizeof(buf), 0, 0, false);
+	send_packet(tx, &dest, first, sizeof(first));
+	recv_packet(rx, buf, 0, MSG_TRUNC, sizeof(first), true);
+	expect_empty(rx);
+	wait_sent(tx);
+	close(tx);
+	close(rx);
+}
+
+void test_dgram_receive_flags(const struct test_opts *opts)
+{
+	int rx = bound_socket(VMADDR_CID_ANY);
+	int tx = bound_socket(VMADDR_CID_ANY);
+	struct sockaddr_vm dest = local_address(rx);
+	const int flags[] = { MSG_OOB, MSG_ERRQUEUE };
+	char byte;
+	size_t i;
+
+	send_packet(tx, &dest, "A", 1);
+	wait_readable(rx);
+	for (i = 0; i < ARRAY_SIZE(flags); i++) {
+		expect_error(recv(rx, &byte, 1, flags[i] | MSG_DONTWAIT),
+			     EOPNOTSUPP, "unsupported receive flag");
+		expect_inq(rx, 1);
+	}
+	recv_packet(rx, &byte, 1, 0, 1, false);
+	if (byte != 'A')
+		fail("unsupported flags consumed the normal packet");
+	close(tx);
+	close(rx);
+}
+
+void test_dgram_vmci_inq(const struct test_opts *opts)
+{
+	int rx = bound_socket(VMADDR_CID_ANY);
+	int tx = bound_socket(VMADDR_CID_ANY);
+	struct sockaddr_vm dest = sock_name(rx, false);
+	char first[31], second[79], buf[80];
+
+	memset(first, 'A', sizeof(first));
+	memset(second, 'B', sizeof(second));
+	/* CID_ANY is resolved by the dedicated transport to its local CID. */
+	dest.svm_cid = VMADDR_CID_ANY;
+	send_packet(tx, &dest, first, sizeof(first));
+	send_packet(tx, &dest, second, sizeof(second));
+	wait_readable(rx);
+	expect_inq(rx, sizeof(first));
+	recv_packet(rx, buf, sizeof(buf), 0, sizeof(first), false);
+	if (memcmp(buf, first, sizeof(first)))
+		fail("VMCI first payload mismatch");
+	wait_readable(rx);
+	expect_inq(rx, sizeof(second));
+	recv_packet(rx, buf, sizeof(buf), 0, sizeof(second), false);
+	if (memcmp(buf, second, sizeof(second)))
+		fail("VMCI second payload mismatch");
+	send_packet(tx, &dest, "", 0);
+	wait_readable(rx);
+	expect_inq(rx, 0);
+	recv_packet(rx, buf, sizeof(buf), 0, 0, false);
+	expect_empty(rx);
+	close(tx);
+	close(rx);
+}
+
+void test_dgram_filter(const struct test_opts *opts)
+{
+	struct sock_filter insn[] = { BPF_STMT(BPF_RET | BPF_K, 7) };
+	struct sock_fprog prog = { .len = ARRAY_SIZE(insn), .filter = insn };
+	int rx = bound_socket(VMADDR_CID_ANY);
+	int tx = bound_socket(VMADDR_CID_ANY);
+	struct sockaddr_vm dest = local_address(rx);
+	char data[100], buf[100];
+
+	memset(data, 'F', sizeof(data));
+	check(setsockopt(rx, SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog)),
+	      "SO_ATTACH_FILTER");
+	send_packet(tx, &dest, data, sizeof(data));
+	wait_readable(rx);
+	expect_inq(rx, 7);
+	recv_packet(rx, buf, 1, MSG_PEEK | MSG_TRUNC, 7, true);
+	expect_inq(rx, 7);
+	memset(buf, 0, sizeof(buf));
+	recv_packet(rx, buf, sizeof(buf), MSG_TRUNC, 7, false);
+	if (memcmp(buf, data, 7) || buf[7])
+		fail("filtered receive copied beyond the filtered payload");
+	send_packet(tx, &dest, data, sizeof(data));
+	recv_packet(rx, buf, 1, MSG_TRUNC, 7, true);
+	expect_empty(rx);
+	close(tx);
+	close(rx);
+}
+
+static void check_peer_filter(int rx, int allowed, int stray)
+{
+	struct sockaddr_vm dest = local_address(rx);
+	char byte;
+
+	/* Both packets use the same ordered loopback worker. */
+	send_packet(stray, &dest, "B", 1);
+	send_packet(allowed, &dest, "A", 1);
+	recv_packet(rx, &byte, 1, 0, 1, false);
+	if (byte != 'A')
+		fail("connected socket accepted a different peer");
+	expect_empty(rx);
+}
+
+void test_dgram_connect(const struct test_opts *opts)
+{
+	int rx = bound_socket(VMADDR_CID_ANY);
+	int allowed = bound_socket(VMADDR_CID_ANY);
+	int stray = bound_socket(VMADDR_CID_ANY);
+	struct sockaddr_vm peer = local_address(allowed);
+	struct sockaddr_vm dest = local_address(rx);
+	char byte;
+
+	connect_fd(rx, &peer);
+	check_peer_filter(rx, allowed, stray);
+	disconnect_fd(rx);
+	send_packet(stray, &dest, "B", 1);
+	recv_packet(rx, &byte, 1, 0, 1, false);
+	if (byte != 'B')
+		fail("disconnect did not remove peer filtering");
+	connect_fd(rx, &peer);
+	check(shutdown(rx, SHUT_WR), "shutdown SHUT_WR");
+	if (poll_fd(rx, POLLOUT, 0) & POLLOUT)
+		fail("write-shutdown socket is still writable");
+	check_peer_filter(rx, allowed, stray);
+	check(shutdown(rx, SHUT_RD), "shutdown SHUT_RD");
+	if (!(poll_fd(rx, POLLIN | POLLRDHUP, 0) & POLLRDHUP))
+		fail("read shutdown did not report POLLRDHUP");
+	close(stray);
+	close(allowed);
+	close(rx);
+}
+
+void test_dgram_source_cid(const struct test_opts *opts)
+{
+	unsigned int cids[] = { VMADDR_CID_LOCAL, local_cid() };
+	size_t i;
+
+	for (i = 0; i < ARRAY_SIZE(cids); i++) {
+		int rx = bound_socket(VMADDR_CID_ANY);
+		int tx = bound_socket(cids[i]);
+		struct sockaddr_vm dest = local_address(rx);
+		struct sockaddr_vm src;
+		char byte;
+
+		send_packet(tx, &dest, "A", 1);
+		src = recv_packet(rx, &byte, 1, 0, 1, false);
+		if (src.svm_cid != cids[i] || src.svm_flags ||
+		    src.svm_port != sock_name(tx, false).svm_port)
+			fail("explicit loopback source CID %u was not preserved", cids[i]);
+		send_packet(rx, &src, "R", 1);
+		recv_packet(tx, &byte, 1, 0, 1, false);
+		if (byte != 'R')
+			fail("reply to explicit source CID failed");
+		close(tx);
+		close(rx);
+	}
+}
+
+void test_dgram_bound_lookup(const struct test_opts *opts)
+{
+	const int types[] = { SOCK_STREAM, SOCK_SEQPACKET };
+	unsigned int cid = local_cid();
+	size_t i;
+
+	for (i = 0; i < ARRAY_SIZE(types); i++) {
+		int listener = vsock_bind(VMADDR_CID_ANY, VMADDR_PORT_ANY, types[i]);
+		int stream = vsock_bind(VMADDR_CID_LOCAL, VMADDR_PORT_ANY, types[i]);
+		struct sockaddr_vm listen_addr = local_address(listener);
+		struct sockaddr_vm src = sock_name(stream, false);
+		struct sockaddr_vm dest;
+		int accepted, tx, rx;
+		char byte;
+
+		check(listen(listener, 1), "listen");
+		if (vsock_connect_fd(stream, listen_addr.svm_cid, listen_addr.svm_port))
+			fail("%s loopback connect: %s",
+			     types[i] == SOCK_STREAM ? "stream" : "seqpacket", strerror(errno));
+		wait_readable(listener);
+		accepted = accept4(listener, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC);
+		check(accepted, "accept");
+		if (sock_name(accepted, true).svm_cid != VMADDR_CID_LOCAL ||
+		    sock_name(accepted, true).svm_port != src.svm_port)
+			fail("unexpected accepted loopback peer");
+		close(listener);
+		rx = dgram_socket();
+		bind_fd(rx, cid, src.svm_port);
+		dest = sock_name(rx, false);
+		tx = dgram_socket();
+		/* Reuse the listener's port for the datagram source. The receive
+		 * socket uses the stream client's port at another local CID.
+		 * Connected lookup ignores the destination CID, so it would find
+		 * the stream client before the bound datagram socket.
+		 */
+		bind_fd(tx, VMADDR_CID_LOCAL, listen_addr.svm_port);
+		send_packet(tx, &dest, "D", 1);
+		recv_packet(rx, &byte, 1, 0, 1, false);
+		if (byte != 'D' || (poll_fd(stream, POLLIN, 0) & POLLIN) ||
+		    (poll_fd(accepted, POLLIN, 0) & POLLIN))
+			fail("connected socket shadowed the reused datagram port");
+		send_byte(stream, 1, 0);
+		wait_readable(accepted);
+		recv_byte(accepted, 1, 0);
+		send_byte(accepted, 1, 0);
+		wait_readable(stream);
+		recv_byte(stream, 1, 0);
+		close(tx);
+		close(rx);
+		close(accepted);
+		close(stream);
+	}
+}
+
+void test_dgram_sockmap(const struct test_opts *opts)
+{
+	const int types[] = { BPF_MAP_TYPE_SOCKMAP, BPF_MAP_TYPE_SOCKHASH };
+	int fd = bound_socket(VMADDR_CID_ANY);
+	struct sockaddr_vm addr = local_address(fd);
+	uint64_t value = fd;
+	uint32_t key = 0;
+	size_t i;
+
+	connect_fd(fd, &addr);
+	for (i = 0; i < ARRAY_SIZE(types); i++) {
+		union bpf_attr attr = {
+			.map_type = types[i],
+			.key_size = sizeof(key),
+			.value_size = sizeof(value),
+			.max_entries = 1,
+		};
+		int map = syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));
+
+		check(map, "BPF_MAP_CREATE (requires BPF privileges and CONFIG_BPF_SYSCALL)");
+		memset(&attr, 0, sizeof(attr));
+		attr.map_fd = map;
+		attr.key = (uintptr_t)&key;
+		attr.value = (uintptr_t)&value;
+		attr.flags = BPF_ANY;
+		expect_error(syscall(__NR_bpf, BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)),
+			     EOPNOTSUPP, "datagram sockmap admission");
+		close(map);
+	}
+	close(fd);
+}
+
+void test_dgram_nested_source(const struct test_opts *opts)
+{
+	int rx, tx;
+	struct sockaddr_vm dest;
+
+	if (local_cid() <= VMADDR_CID_HOST)
+		fail("this test requires both a guest frontend and vhost_vsock");
+	rx = bound_socket(VMADDR_CID_LOCAL);
+	tx = bound_socket(VMADDR_CID_HOST);
+	dest = local_address(rx);
+	expect_error(sendto(tx, "A", 1, MSG_NOSIGNAL,
+			    (struct sockaddr *)&dest, sizeof(dest)),
+		     EADDRNOTAVAIL, "nested CID_HOST loopback send");
+	expect_error(connect(tx, (struct sockaddr *)&dest, sizeof(dest)),
+		     EADDRNOTAVAIL, "nested CID_HOST loopback connect");
+	if (queue_bytes(tx, SIOCOUTQ))
+		fail("rejected route left pending output");
+	expect_empty(rx);
+	close(tx);
+	close(rx);
+}
+
+static int peer_socket(const struct test_opts *opts, struct sockaddr_vm *peer)
+{
+	int fd = bound_socket(opts->bind_cid);
+
+	control_writeulong(sock_name(fd, false).svm_port);
+	*peer = address(opts->peer_cid, control_readulong());
+	if (opts->to_host)
+		peer->svm_flags = VMADDR_FLAG_TO_HOST;
+	return fd;
+}
+
+static void check_source(const struct test_opts *opts,
+			 const struct sockaddr_vm *peer, const struct sockaddr_vm *src)
+{
+	bool upstream = opts->expect_to_host || opts->peer_cid == VMADDR_CID_HOST;
+
+	if (src->svm_cid != peer->svm_cid || src->svm_port != peer->svm_port ||
+	    src->svm_flags != (upstream ? VMADDR_FLAG_TO_HOST : 0))
+		fail("source expected %u:%u flags=%u, got %u:%u flags=%u",
+		     peer->svm_cid, peer->svm_port, upstream ? VMADDR_FLAG_TO_HOST : 0,
+		     src->svm_cid, src->svm_port, src->svm_flags);
+}
+
+static void fill_payload(char *buf, size_t len, unsigned int seed)
+{
+	size_t i;
+
+	for (i = 0; i < len; i++)
+		buf[i] = (i * 131 + seed) & 0xff;
+}
+
+void test_dgram_sizes(const struct test_opts *opts)
+{
+	const size_t sizes[] = { 0, 1, 7, 255, 4095, 4096, 4097,
+				8192, 32768, 65535, MAX_DGRAM };
+	char *data = malloc(MAX_DGRAM + 1);
+	char *buf = malloc(MAX_DGRAM + 1);
+	struct sockaddr_vm peer, src;
+	unsigned int round;
+	int fd;
+	size_t i;
+
+	if (!data || !buf)
+		fail("malloc");
+	fd = peer_socket(opts, &peer);
+	fill_payload(data, MAX_DGRAM + 1, 0);
+	expect_error(sendto(fd, data, MAX_DGRAM + 1, MSG_NOSIGNAL,
+			    (struct sockaddr *)&peer, sizeof(peer)),
+		     EMSGSIZE, "oversized datagram");
+	if (queue_bytes(fd, SIOCOUTQ))
+		fail("oversized datagram left pending output");
+	/* More than a default RX ring's worth, including on small rings. */
+	for (round = 0; round < 32; round++) {
+		for (i = 0; i < ARRAY_SIZE(sizes); i++) {
+			fill_payload(data, sizes[i], round * ARRAY_SIZE(sizes) + i);
+			if (opts->mode == TEST_MODE_CLIENT)
+				send_packet(fd, &peer, data, sizes[i]);
+			src = recv_packet(fd, buf, MAX_DGRAM + 1, 0, sizes[i], false);
+			check_source(opts, &peer, &src);
+			if (memcmp(data, buf, sizes[i]))
+				fail("payload mismatch: size %zu, round %u", sizes[i], round);
+			if (opts->mode == TEST_MODE_SERVER)
+				send_packet(fd, &src, buf, sizes[i]);
+		}
+	}
+	wait_sent(fd);
+	expect_empty(fd);
+	close(fd);
+	free(buf);
+	free(data);
+}
+
+void test_dgram_routes(const struct test_opts *opts)
+{
+	struct sockaddr_vm peer, src, stored, local;
+	int fd = peer_socket(opts, &peer);
+	int aux = bound_socket(VMADDR_CID_ANY);
+	unsigned int round;
+	char byte;
+
+	connect_fd(fd, &peer);
+	stored = sock_name(fd, true);
+	local = local_address(aux);
+	for (round = 0; round < 32; round++) {
+		send_packet(fd, &local, "L", 1);
+		recv_packet(aux, &byte, 1, 0, 1, false);
+		if (byte != 'L')
+			fail("sendto on a connected socket did not use loopback");
+		src = sock_name(fd, true);
+		if (src.svm_cid != stored.svm_cid || src.svm_port != stored.svm_port ||
+		    src.svm_flags != stored.svm_flags)
+			fail("per-message route changed the connected peer");
+		send_packet(fd, NULL, "P", 1);
+		src = recv_packet(fd, &byte, 1, 0, 1, false);
+		check_source(opts, &peer, &src);
+		if (byte != 'P')
+			fail("connected send did not retain the peer route");
+	}
+	wait_sent(fd);
+	close(aux);
+	close(fd);
+}
+
+void test_dgram_peer_filter(const struct test_opts *opts)
+{
+	struct sockaddr_vm peer, src;
+	int fd = peer_socket(opts, &peer);
+	int stray = bound_socket(opts->bind_cid);
+	char byte;
+
+	connect_fd(fd, &peer);
+	control_writeln("CONNECTED");
+	control_expectln("CONNECTED");
+	send_packet(stray, &peer, "B", 1);
+	send_packet(fd, NULL, "A", 1);
+	src = recv_packet(fd, &byte, 1, 0, 1, false);
+	check_source(opts, &peer, &src);
+	if (byte != 'A')
+		fail("connected socket accepted a different peer port");
+	disconnect_fd(fd);
+	control_writeln("DISCONNECTED");
+	control_expectln("DISCONNECTED");
+	send_packet(stray, &peer, "B", 1);
+	src = recv_packet(fd, &byte, 1, 0, 1, false);
+	if (byte != 'B' || src.svm_cid != peer.svm_cid || src.svm_port == peer.svm_port)
+		fail("disconnected socket did not accept the new peer");
+	close(stray);
+	close(fd);
+}
diff --git a/tools/testing/vsock/vsock_test_dgram.h b/tools/testing/vsock/vsock_test_dgram.h
new file mode 100644
index 000000000000..0dec74ef5f46
--- /dev/null
+++ b/tools/testing/vsock/vsock_test_dgram.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef VSOCK_TEST_DGRAM_H
+#define VSOCK_TEST_DGRAM_H
+
+#include "util.h"
+
+const char *vsock_dgram_check_local(const struct test_opts *opts);
+const char *vsock_dgram_check_peer(const struct test_opts *opts);
+const char *vsock_dgram_check_routes(const struct test_opts *opts);
+const char *vsock_dgram_check_vmci(const struct test_opts *opts);
+
+void test_dgram_bind(const struct test_opts *opts);
+void test_dgram_receive(const struct test_opts *opts);
+void test_dgram_receive_flags(const struct test_opts *opts);
+void test_dgram_filter(const struct test_opts *opts);
+void test_dgram_connect(const struct test_opts *opts);
+void test_dgram_source_cid(const struct test_opts *opts);
+void test_dgram_bound_lookup(const struct test_opts *opts);
+void test_dgram_sockmap(const struct test_opts *opts);
+void test_dgram_nested_source(const struct test_opts *opts);
+void test_dgram_sizes(const struct test_opts *opts);
+void test_dgram_routes(const struct test_opts *opts);
+void test_dgram_peer_filter(const struct test_opts *opts);
+void test_dgram_vmci_inq(const struct test_opts *opts);
+
+#endif /* VSOCK_TEST_DGRAM_H */

-- 
2.56.0


  parent reply	other threads:[~2026-10-28 16:05 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-28 16:04 [PATCH RFC 0/8] vsock: add datagram support to virtio and vhost Sergio Lopez
2026-10-28 16:04 ` [PATCH RFC 1/8] vsock: expose the core port allocator to transports Sergio Lopez
2026-10-28 16:04 ` [PATCH RFC 2/8] vsock: report datagram receive and send queue state Sergio Lopez
2026-10-28 16:04 ` [PATCH RFC 3/8] vsock/virtio: implement the datagram data path Sergio Lopez
2026-10-28 16:04 ` [PATCH RFC 4/8] vsock/virtio: negotiate datagram support on the guest Sergio Lopez
2026-10-28 16:04 ` [PATCH RFC 5/8] vhost/vsock: negotiate datagram support with guests Sergio Lopez
2026-10-28 16:04 ` [PATCH RFC 6/8] vsock: allow datagrams to use directional transports Sergio Lopez
2026-10-28 16:04 ` [PATCH RFC 7/8] vsock: preserve host-to-guest datagram boundaries Sergio Lopez
2026-10-28 16:04 ` Sergio Lopez [this message]
2026-10-28 17:25 ` [PATCH RFC 0/8] vsock: add datagram support to virtio and vhost Alexei Starovoitov

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261028-vsock-dgram-support-v1-8-e457cf7cee78@sinrega.org \
    --to=slp@redhat.com \
    --cc=bcm-kernel-feedback-list@broadcom.com \
    --cc=bobbyeshleman@meta.com \
    --cc=bpf@vger.kernel.org \
    --cc=bryan-bt.tan@broadcom.com \
    --cc=davem@davemloft.net \
    --cc=decui@microsoft.com \
    --cc=edumazet@kernel.org \
    --cc=eperezma@redhat.com \
    --cc=haiyangz@microsoft.com \
    --cc=horms@kernel.org \
    --cc=jasowangio@gmail.com \
    --cc=kuba@kernel.org \
    --cc=kvm@vger.kernel.org \
    --cc=linux-hyperv@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mst@redhat.com \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=sgarzare@redhat.com \
    --cc=stefanha@redhat.com \
    --cc=virtualization@lists.linux.dev \
    --cc=vishnu.dasa@broadcom.com \
    --cc=wei.liu@kernel.org \
    --cc=xuanzhuo@linux.alibaba.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®