* [PATCH RFC v2 0/8] vsock: add datagram support to virtio and vhost
@ 2026-11-13 12:47 Sergio Lopez
2026-11-13 12:47 ` [PATCH RFC v2 1/8] vsock: expose the core port allocator to transports Sergio Lopez
` (8 more replies)
0 siblings, 9 replies; 13+ messages in thread
From: Sergio Lopez @ 2026-11-13 12:47 UTC (permalink / raw)
To: Stefano Garzarella, Bobby Eshleman, Alexei Starovoitov,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Bryan Tan, Vishnu Dasa,
Broadcom internal kernel review list, Stefan Hajnoczi,
Michael S. Tsirkin, Jason Wang, Eugenio Pérez, Xuan Zhuo,
Haiyang Zhang, Wei Liu, Dexuan Cui, Shuah Khan, Randy Dunlap
Cc: virtualization, netdev, linux-kernel, kvm, bpf, linux-hyperv,
Jonathan Corbet, linux-doc, Sergio Lopez
This RFC adds SOCK_DGRAM support to virtio-vsock and vhost-vsock through
the existing AF_VSOCK API. It is intended for guest and host services
that exchange independent requests, responses, or events and need message
boundaries and sender addresses without a stream connection or application
framing.
VIRTIO_VSOCK_F_DGRAM gates virtio traffic in both directions. Each virtio
send carries zero to 64 KiB of payload in one VIRTIO_VSOCK_TYPE_DGRAM
packet. On the RX virtqueue, the device may split a datagram across
page-sized guest buffers.
START and EOM delimit the message; the guest reassembles at most 64
fragments and 64 KiB before delivering it to a socket. Invalid or
incomplete messages are discarded. Datagram delivery is best effort:
there are no peer credits, acknowledgements, or retransmissions. Send
admission uses the socket send-memory limit, and SIOCOUTQ reports charged
send memory.
Datagram sockets have a separate port space from stream and seqpacket
sockets. The latter two continue to share their port space. Datagram
routes are chosen per destination among guest-to-host, host-to-guest, and
loopback transports, with VMCI as a fallback. VMCI still owns its socket
handle when it is present; loopback works without a directional transport.
Hyper-V continues to reject datagram sockets.
Bobby Eshleman and Amery Hung explored this feature in their v6 series
and v7 work-in-progress branch. This series is a fresh implementation
against the current vsock code. It follows the separate datagram port
space, per-destination routing, and VMCI fallback requirements discussed
there. For guest receive buffers, it uses bounded fragmentation and
reassembly rather than full-message buffers or the earlier drop/error
queue approach:
https://lore.kernel.org/r/20240710212555.1617795-1-amery.hung@bytedance.com
https://github.com/ameryhung/linux/tree/vsock-dgram-v7
A corresponding virtio specification update has been drafted separately.
The wire format and feature negotiation still need specification review
before this series can be considered for merging.
The first patches expose the core port allocator and add datagram queue
reporting, then implement the common data path, guest and host feature
negotiation, routing, and RX fragmentation. The last patch extends
vsock_test with local and peer tests for message boundaries, routing,
connected peers, port sharing, queue accounting, and delayed reads.
Testing used QEMU with the F_DGRAM property patch below, an x86-64
KASAN/lockdep kernel, paired guest tests, and a nested Linux vhost-vsock
backend. The tested paths include 64 KiB messages, delayed-read bursts,
feature-disabled ingress, stream and seqpacket controls, and header-only
guest RX buffers. A temporary userspace backend still fails stream test
21 on both the series base and tip; that case passes with Linux vhost.
Real VMCI and Hyper-V hardware were not exercised.
https://lists.gnu.org/archive/html/qemu-devel/2026-10/msg08074.html
Code was written and reviewed both manually and assisted by Codex
(GPT-6.1-Sol) and Claude (Opus 4.6).
Signed-off-by: Sergio Lopez <slp@redhat.com>
---
Changes in v2:
- Keep guest RX buffers page-sized; add START/EOM framing and bounded
datagram reassembly (thanks to Alexei Starovoitov and Bobby Eshleman).
- Preserve stream and seqpacket data when an RX buffer has no payload
room.
- Separate datagram and connection-oriented port spaces, and share
receive metadata and queueing (thanks to Alexei Starovoitov).
- Check negotiated support and destination CID on ingress, and require
an exact connected peer CID and port (thanks to Sashiko AI bot).
- Select a capable route for each destination, retain VMCI as fallback,
and support loopback-only sockets (thanks to Alexei Starovoitov).
- Preserve the connected peer address across temporary parent fallback
(thanks to Sashiko AI bot).
- Use socket write-memory accounting for datagram SIOCOUTQ instead of
separate transport counters (thanks to Alexei Starovoitov).
- Extend selftests for fragmentation, delayed reads, port sharing,
connected peers, and capability-based skips; fix the test build and
README entries (thanks to Alexei Starovoitov, Bobby Eshleman,
and Sashiko AI bot).
- Link to v1: https://lore.kernel.org/r/20261028-vsock-dgram-support-v1-0-e457cf7cee78@sinrega.org
---
Sergio Lopez (8):
vsock: expose the core port allocator to transports
vsock: report datagram receive and send queue state
vsock/virtio: implement the datagram data path
vsock/virtio: negotiate datagram support on the guest
vhost/vsock: negotiate datagram support with guests
vsock: allow datagrams to use directional transports
vsock: fragment datagrams across receive buffers
vsock: add datagram tests to vsock_test
Documentation/admin-guide/sysctl/net.rst | 8 +-
drivers/vhost/vsock.c | 51 +-
include/linux/virtio_vsock.h | 9 +-
include/net/af_vsock.h | 30 +-
include/uapi/linux/virtio_vsock.h | 10 +
net/vmw_vsock/af_vsock.c | 459 +++++++++++---
net/vmw_vsock/hyperv_transport.c | 11 +-
net/vmw_vsock/virtio_transport.c | 167 ++++-
net/vmw_vsock/virtio_transport_common.c | 164 ++++-
net/vmw_vsock/vmci_transport.c | 115 ++--
net/vmw_vsock/vsock_bpf.c | 3 +-
net/vmw_vsock/vsock_loopback.c | 2 +-
tools/testing/vsock/Makefile | 3 +-
tools/testing/vsock/README | 31 +-
tools/testing/vsock/util.c | 62 +-
tools/testing/vsock/util.h | 10 +
tools/testing/vsock/vsock_test.c | 175 +++++-
tools/testing/vsock/vsock_test_dgram.c | 1000 ++++++++++++++++++++++++++++++
tools/testing/vsock/vsock_test_dgram.h | 29 +
19 files changed, 2066 insertions(+), 273 deletions(-)
---
base-commit: 65ba8f34ec886ec72b2c07ddc8cf3d815c32869d
change-id: 20261027-vsock-dgram-support-683a4e0aebe7
Best regards,
--
Sergio Lopez <slp@redhat.com>
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH RFC v2 1/8] vsock: expose the core port allocator to transports
2026-11-13 12:47 [PATCH RFC v2 0/8] vsock: add datagram support to virtio and vhost Sergio Lopez
@ 2026-11-13 12:47 ` Sergio Lopez
2026-11-13 12:47 ` [PATCH RFC v2 2/8] vsock: report datagram receive and send queue state Sergio Lopez
` (7 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Sergio Lopez @ 2026-11-13 12:47 UTC (permalink / raw)
To: Stefano Garzarella, Bobby Eshleman, Alexei Starovoitov,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Bryan Tan, Vishnu Dasa,
Broadcom internal kernel review list, Stefan Hajnoczi,
Michael S. Tsirkin, Jason Wang, Eugenio Pérez, Xuan Zhuo,
Haiyang Zhang, Wei Liu, Dexuan Cui, Shuah Khan, Randy Dunlap
Cc: virtualization, netdev, linux-kernel, kvm, bpf, linux-hyperv,
Jonathan Corbet, linux-doc, Sergio Lopez
The core port allocator is currently private to connection-oriented
sockets. Virtio datagrams need the core bound table and port allocation,
while VMCI datagrams must keep their own endpoint creation and port
namespace.
Export vsock_bind_common() with the bound-table locking inside the
helper, and use it for stream and SEQPACKET binds. Keep datagram binds
delegated to the transport so a transport can opt into the core
allocator without changing other transports' binding behavior.
Keep datagram and connection-oriented sockets in separate logical port
spaces within the core bound table. Filter bind conflicts and bound-socket
lookups by class, including wildcard CIDs and automatic port allocation,
so datagram and stream/SEQPACKET sockets can share a CID and port. Stream
and SEQPACKET sockets continue to conflict with each other.
Signed-off-by: Sergio Lopez <slp@redhat.com>
---
include/net/af_vsock.h | 3 ++
net/vmw_vsock/af_vsock.c | 98 +++++++++++++++++++++++++++++++-----------------
2 files changed, 66 insertions(+), 35 deletions(-)
diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h
index e8481f12312e..ac061115ed30 100644
--- a/include/net/af_vsock.h
+++ b/include/net/af_vsock.h
@@ -220,6 +220,7 @@ void vsock_remove_pending(struct sock *listener, struct sock *pending);
void vsock_enqueue_accept(struct sock *listener, struct sock *connected);
void vsock_pending_to_accept(struct sock *listener, struct sock *pending);
void vsock_insert_connected(struct vsock_sock *vsk);
+int vsock_bind_common(struct vsock_sock *vsk, struct sockaddr_vm *addr);
void vsock_remove_bound(struct vsock_sock *vsk);
void vsock_remove_connected(struct vsock_sock *vsk);
struct sock *vsock_find_bound_socket(struct sockaddr_vm *addr);
@@ -227,6 +228,8 @@ struct sock *vsock_find_connected_socket(struct sockaddr_vm *src,
struct sockaddr_vm *dst);
struct sock *vsock_find_bound_socket_net(struct sockaddr_vm *addr,
struct net *net);
+struct sock *vsock_find_bound_dgram_socket_net(struct sockaddr_vm *addr,
+ struct net *net);
struct sock *vsock_find_connected_socket_net(struct sockaddr_vm *src,
struct sockaddr_vm *dst,
struct net *net);
diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
index d0508887a2fb..d5cf240d4ece 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -14,14 +14,10 @@
* specified an address that they are responsible for) and one for connected
* sockets (sockets that have established a connection with another socket).
* These tables are "global" in that all sockets on the system are placed
- * within them. - Note, though, that the bound table contains an extra entry
- * for a list of unbound sockets and SOCK_DGRAM sockets will always remain in
- * that list. The bound table is used solely for lookup of sockets when packets
- * are received and that's not necessary for SOCK_DGRAM sockets since we create
- * a datagram handle for each and need not perform a lookup. Keeping SOCK_DGRAM
- * sockets out of the bound hash buckets will reduce the chance of collisions
- * when looking for SOCK_STREAM sockets and prevents us from having to check the
- * socket type in the hash table lookups.
+ * within them. The bound table has an extra entry for unbound sockets.
+ * Transports with their own datagram endpoint table can leave their datagram
+ * sockets there; virtio datagrams use the bound hash buckets. Datagram and
+ * connection-oriented sockets have separate logical port spaces within them.
*
* - Sockets created by user action will either be "client" sockets that
* initiate a connection or "server" sockets that listen for connections; we do
@@ -295,12 +291,16 @@ static void __vsock_remove_connected(struct vsock_sock *vsk)
}
static struct sock *__vsock_find_bound_socket_net(struct sockaddr_vm *addr,
- struct net *net)
+ struct net *net, bool dgram)
{
struct vsock_sock *vsk;
list_for_each_entry(vsk, vsock_bound_sockets(addr), bound_table) {
struct sock *sk = sk_vsock(vsk);
+ bool socket_dgram = sk->sk_type == SOCK_DGRAM;
+
+ if (socket_dgram != dgram)
+ continue;
if (vsock_addr_equals_addr(addr, &vsk->local_addr) &&
vsock_net_check_mode(sock_net(sk), net))
@@ -372,28 +372,41 @@ void vsock_remove_connected(struct vsock_sock *vsk)
}
EXPORT_SYMBOL_GPL(vsock_remove_connected);
-/* Find a bound socket, filtering by namespace and namespace mode.
- *
- * Use this in transports that are namespace-aware and can provide the
- * network namespace context.
- */
-struct sock *vsock_find_bound_socket_net(struct sockaddr_vm *addr,
- struct net *net)
+static struct sock *vsock_find_bound_socket_type_net(struct sockaddr_vm *addr,
+ struct net *net, bool dgram)
{
struct sock *sk;
spin_lock_bh(&vsock_table_lock);
- sk = __vsock_find_bound_socket_net(addr, net);
+ sk = __vsock_find_bound_socket_net(addr, net, dgram);
if (sk)
sock_hold(sk);
-
spin_unlock_bh(&vsock_table_lock);
return sk;
}
+
+/* Find a connection-oriented bound socket, filtering by namespace and mode.
+ *
+ * Use this in transports that are namespace-aware and can provide the
+ * network namespace context.
+ */
+struct sock *vsock_find_bound_socket_net(struct sockaddr_vm *addr,
+ struct net *net)
+{
+ return vsock_find_bound_socket_type_net(addr, net, false);
+}
EXPORT_SYMBOL_GPL(vsock_find_bound_socket_net);
-/* Find a bound socket without namespace filtering.
+/* Find a datagram bound socket, filtering by namespace and mode. */
+struct sock *vsock_find_bound_dgram_socket_net(struct sockaddr_vm *addr,
+ struct net *net)
+{
+ return vsock_find_bound_socket_type_net(addr, net, true);
+}
+EXPORT_SYMBOL_GPL(vsock_find_bound_dgram_socket_net);
+
+/* Find a connection-oriented bound socket without namespace filtering.
*
* Use this in transports that lack namespace context. All sockets are
* treated as if in global mode.
@@ -832,11 +845,13 @@ static void vsock_pending_work(struct work_struct *work)
/**** SOCKET OPERATIONS ****/
-static int __vsock_bind_connectible(struct vsock_sock *vsk,
- struct sockaddr_vm *addr)
+static int __vsock_bind_common(struct vsock_sock *vsk,
+ struct sockaddr_vm *addr)
{
- struct net *net = sock_net(sk_vsock(vsk));
+ struct sock *sk = sk_vsock(vsk);
+ struct net *net = sock_net(sk);
struct sockaddr_vm new_addr;
+ bool dgram = sk->sk_type == SOCK_DGRAM;
if (!net->vsock.port)
net->vsock.port = get_random_u32_above(LAST_RESERVED_PORT);
@@ -854,7 +869,8 @@ static int __vsock_bind_connectible(struct vsock_sock *vsk,
new_addr.svm_port = net->vsock.port++;
- if (!__vsock_find_bound_socket_net(&new_addr, net)) {
+ if (!__vsock_find_bound_socket_net(&new_addr, net,
+ dgram)) {
found = true;
break;
}
@@ -871,16 +887,15 @@ static int __vsock_bind_connectible(struct vsock_sock *vsk,
return -EACCES;
}
- if (__vsock_find_bound_socket_net(&new_addr, net))
+ if (__vsock_find_bound_socket_net(&new_addr, net, dgram))
return -EADDRINUSE;
}
vsock_addr_init(&vsk->local_addr, new_addr.svm_cid, new_addr.svm_port);
- /* Remove connection oriented sockets from the unbound list and add them
- * to the hash table for easy lookup by its address. The unbound list
- * is simply an extra entry at the end of the hash table, a trick used
- * by AF_UNIX.
+ /* Move the socket from the unbound list to the hash table for lookup
+ * by its address. The unbound list is an extra entry at the end of
+ * the hash table, a trick used by AF_UNIX.
*/
__vsock_remove_bound(vsk);
__vsock_insert_bound(vsock_bound_sockets(&vsk->local_addr), vsk);
@@ -888,11 +903,26 @@ static int __vsock_bind_connectible(struct vsock_sock *vsk,
return 0;
}
-static int __vsock_bind_dgram(struct vsock_sock *vsk,
- struct sockaddr_vm *addr)
+/**
+ * vsock_bind_common - bind a socket using the core port allocator
+ * @vsk: socket to bind
+ * @addr: local address to bind to
+ *
+ * The caller must hold the socket lock.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int vsock_bind_common(struct vsock_sock *vsk, struct sockaddr_vm *addr)
{
- return vsk->transport->dgram_bind(vsk, addr);
+ int ret;
+
+ spin_lock_bh(&vsock_table_lock);
+ ret = __vsock_bind_common(vsk, addr);
+ spin_unlock_bh(&vsock_table_lock);
+
+ return ret;
}
+EXPORT_SYMBOL_GPL(vsock_bind_common);
static int __vsock_bind(struct sock *sk, struct sockaddr_vm *addr)
{
@@ -914,13 +944,11 @@ static int __vsock_bind(struct sock *sk, struct sockaddr_vm *addr)
switch (sk->sk_socket->type) {
case SOCK_STREAM:
case SOCK_SEQPACKET:
- spin_lock_bh(&vsock_table_lock);
- retval = __vsock_bind_connectible(vsk, addr);
- spin_unlock_bh(&vsock_table_lock);
+ retval = vsock_bind_common(vsk, addr);
break;
case SOCK_DGRAM:
- retval = __vsock_bind_dgram(vsk, addr);
+ retval = vsk->transport->dgram_bind(vsk, addr);
break;
default:
--
2.56.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH RFC v2 2/8] vsock: report datagram receive and send queue state
2026-11-13 12:47 [PATCH RFC v2 0/8] vsock: add datagram support to virtio and vhost Sergio Lopez
2026-11-13 12:47 ` [PATCH RFC v2 1/8] vsock: expose the core port allocator to transports Sergio Lopez
@ 2026-11-13 12:47 ` Sergio Lopez
2026-11-13 12:47 ` [PATCH RFC v2 3/8] vsock/virtio: implement the datagram data path Sergio Lopez
` (6 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Sergio Lopez @ 2026-11-13 12:47 UTC (permalink / raw)
To: Stefano Garzarella, Bobby Eshleman, Alexei Starovoitov,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Bryan Tan, Vishnu Dasa,
Broadcom internal kernel review list, Stefan Hajnoczi,
Michael S. Tsirkin, Jason Wang, Eugenio Pérez, Xuan Zhuo,
Haiyang Zhang, Wei Liu, Dexuan Cui, Shuah Khan, Randy Dunlap
Cc: virtualization, netdev, linux-kernel, kvm, bpf, linux-hyperv,
Jonathan Corbet, linux-doc, Sergio Lopez
Datagram receive queues hold whole messages, and transports may charge
queued datagrams to the socket send buffer. Report the size of the
next datagram payload for SIOCINQ under the receive-queue lock, so
concurrent recvmsg() cannot free the skb during the query. Let
transports override the length calculation to exclude headers such as
VMCI's.
Report datagram write readiness only while the send buffer has space.
Set the asynchronous no-space flag when it is full so freeing pending
send buffers can wake poll waiters.
Signed-off-by: Sergio Lopez <slp@redhat.com>
---
include/net/af_vsock.h | 2 ++
net/vmw_vsock/af_vsock.c | 32 ++++++++++++++++++++++++--------
net/vmw_vsock/vmci_transport.c | 9 +++++++++
3 files changed, 35 insertions(+), 8 deletions(-)
diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h
index ac061115ed30..e4fbea3b0e46 100644
--- a/include/net/af_vsock.h
+++ b/include/net/af_vsock.h
@@ -124,6 +124,8 @@ struct vsock_transport {
int (*dgram_enqueue)(struct vsock_sock *, struct sockaddr_vm *,
struct msghdr *, size_t len);
bool (*dgram_allow)(struct vsock_sock *vsk, u32 cid, u32 port);
+ /* Optional payload length override, called with the receive queue locked. */
+ size_t (*dgram_payload_len)(const struct sk_buff *skb);
/* STREAM. */
/* TODO: stream_bind() */
diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
index d5cf240d4ece..8a271eb66860 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -1359,16 +1359,20 @@ static __poll_t vsock_poll(struct file *file, struct socket *sock,
mask |= vsock_poll_shutdown(sk, peer_shutdown);
/* For datagram sockets we can read if there is something in
- * the queue and write as long as the socket isn't shutdown for
- * sending.
+ * the queue and write if the send buffer has space and the
+ * socket isn't shutdown for sending.
*/
if (!skb_queue_empty_lockless(&sk->sk_receive_queue) ||
(sk->sk_shutdown & RCV_SHUTDOWN)) {
mask |= EPOLLIN | EPOLLRDNORM;
}
- if (!(sk->sk_shutdown & SEND_SHUTDOWN))
- mask |= EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND;
+ if (!(sk->sk_shutdown & SEND_SHUTDOWN)) {
+ if (sock_writeable(sk))
+ mask |= EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND;
+ else
+ sk_set_bit(SOCKWQ_ASYNC_NOSPACE, sk);
+ }
} else if (sock_type_connectible(sk->sk_type)) {
const struct vsock_transport *transport;
@@ -1632,10 +1636,22 @@ static int vsock_do_ioctl(struct socket *sock, unsigned int cmd,
break;
}
- n_bytes = vsock_stream_has_data(vsk);
- if (n_bytes < 0) {
- ret = n_bytes;
- break;
+ if (sk->sk_type == SOCK_DGRAM) {
+ struct sk_buff *skb;
+
+ spin_lock_bh(&sk->sk_receive_queue.lock);
+ skb = skb_peek(&sk->sk_receive_queue);
+ if (skb && vsk->transport->dgram_payload_len)
+ n_bytes = vsk->transport->dgram_payload_len(skb);
+ else
+ n_bytes = skb ? skb->len : 0;
+ spin_unlock_bh(&sk->sk_receive_queue.lock);
+ } else {
+ n_bytes = vsock_stream_has_data(vsk);
+ if (n_bytes < 0) {
+ ret = n_bytes;
+ break;
+ }
}
ret = put_user(n_bytes, arg);
break;
diff --git a/net/vmw_vsock/vmci_transport.c b/net/vmw_vsock/vmci_transport.c
index 1f186e8f8364..2b9493e987d1 100644
--- a/net/vmw_vsock/vmci_transport.c
+++ b/net/vmw_vsock/vmci_transport.c
@@ -1761,6 +1761,14 @@ static int vmci_transport_dgram_enqueue(
return err - sizeof(*dg);
}
+static size_t vmci_transport_dgram_payload_len(const struct sk_buff *skb)
+{
+ if (skb->len < sizeof(struct vmci_datagram))
+ return 0;
+
+ return skb->len - sizeof(struct vmci_datagram);
+}
+
static int vmci_transport_dgram_dequeue(struct vsock_sock *vsk,
struct msghdr *msg, size_t len,
int flags)
@@ -2075,6 +2083,7 @@ static struct vsock_transport vmci_transport = {
.connect = vmci_transport_connect,
.dgram_bind = vmci_transport_dgram_bind,
.dgram_dequeue = vmci_transport_dgram_dequeue,
+ .dgram_payload_len = vmci_transport_dgram_payload_len,
.dgram_enqueue = vmci_transport_dgram_enqueue,
.dgram_allow = vmci_transport_dgram_allow,
.stream_dequeue = vmci_transport_stream_dequeue,
--
2.56.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH RFC v2 3/8] vsock/virtio: implement the datagram data path
2026-11-13 12:47 [PATCH RFC v2 0/8] vsock: add datagram support to virtio and vhost Sergio Lopez
2026-11-13 12:47 ` [PATCH RFC v2 1/8] vsock: expose the core port allocator to transports Sergio Lopez
2026-11-13 12:47 ` [PATCH RFC v2 2/8] vsock: report datagram receive and send queue state Sergio Lopez
@ 2026-11-13 12:47 ` Sergio Lopez
2026-11-13 12:47 ` [PATCH RFC v2 4/8] vsock/virtio: negotiate datagram support on the guest Sergio Lopez
` (5 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Sergio Lopez @ 2026-11-13 12:47 UTC (permalink / raw)
To: Stefano Garzarella, Bobby Eshleman, Alexei Starovoitov,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Bryan Tan, Vishnu Dasa,
Broadcom internal kernel review list, Stefan Hajnoczi,
Michael S. Tsirkin, Jason Wang, Eugenio Pérez, Xuan Zhuo,
Haiyang Zhang, Wei Liu, Dexuan Cui, Shuah Khan, Randy Dunlap
Cc: virtualization, netdev, linux-kernel, kvm, bpf, linux-hyperv,
Jonathan Corbet, linux-doc, Sergio Lopez
Add the datagram feature bit and packet type, and implement the common
virtio transport operations needed to bind, send, and receive
datagrams.
Send each message as an OP_RW packet with a maximum 64 KiB payload,
including support for empty messages. Bound pending sends by the
socket send buffer instead of connection-oriented peer credits. Use
the socket's skb memory charge for datagram SIOCOUTQ, including empty
messages, and release it through the normal skb ownership lifetime on
completion, rejection, or queued drops.
Deliver incoming datagrams through the datagram bound-socket lookup
without entering the connection state machine. Connection-oriented
receive lookup excludes datagram sockets, so each class can use the
same destination CID and port. Validate the transport and connected
peer under the socket lock, including after shutdown, and silently
discard undeliverable messages. Use the socket datagram queue for
recvmsg(), including source addresses, peeking, and truncation
reporting based on the payload length after socket filtering. Reject
MSG_OOB and MSG_ERRQUEUE before accessing the receive queue. Reject
datagram sockmap admission until its receive callbacks support it.
Signed-off-by: Sergio Lopez <slp@redhat.com>
---
include/uapi/linux/virtio_vsock.h | 2 +
net/vmw_vsock/af_vsock.c | 31 +++++---
net/vmw_vsock/virtio_transport_common.c | 132 ++++++++++++++++++++++++++++----
net/vmw_vsock/vsock_bpf.c | 3 +-
4 files changed, 140 insertions(+), 28 deletions(-)
diff --git a/include/uapi/linux/virtio_vsock.h b/include/uapi/linux/virtio_vsock.h
index 64738838bee5..27b4b2b8bf13 100644
--- a/include/uapi/linux/virtio_vsock.h
+++ b/include/uapi/linux/virtio_vsock.h
@@ -40,6 +40,7 @@
/* The feature bitmap for virtio vsock */
#define VIRTIO_VSOCK_F_SEQPACKET 1 /* SOCK_SEQPACKET supported */
+#define VIRTIO_VSOCK_F_DGRAM 3 /* SOCK_DGRAM supported */
struct virtio_vsock_config {
__le64 guest_cid;
@@ -69,6 +70,7 @@ struct virtio_vsock_hdr {
enum virtio_vsock_type {
VIRTIO_VSOCK_TYPE_STREAM = 1,
VIRTIO_VSOCK_TYPE_SEQPACKET = 2,
+ VIRTIO_VSOCK_TYPE_DGRAM = 3,
};
enum virtio_vsock_op {
diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
index 8a271eb66860..aef5389eeca2 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -1659,20 +1659,27 @@ static int vsock_do_ioctl(struct socket *sock, unsigned int cmd,
case SIOCOUTQ: {
ssize_t n_bytes;
- if (!vsk->transport || !vsk->transport->unsent_bytes) {
- ret = -EOPNOTSUPP;
- break;
- }
+ if (sk->sk_type == SOCK_DGRAM) {
+ /* Match UDP: report charged send memory, including skb
+ * overhead and empty datagrams.
+ */
+ n_bytes = sk_wmem_alloc_get(sk);
+ } else {
+ if (!vsk->transport || !vsk->transport->unsent_bytes) {
+ ret = -EOPNOTSUPP;
+ break;
+ }
- if (sock_type_connectible(sk->sk_type) && sk->sk_state == TCP_LISTEN) {
- ret = -EINVAL;
- break;
- }
+ if (sk->sk_state == TCP_LISTEN) {
+ ret = -EINVAL;
+ break;
+ }
- n_bytes = vsk->transport->unsent_bytes(vsk);
- if (n_bytes < 0) {
- ret = n_bytes;
- break;
+ n_bytes = vsk->transport->unsent_bytes(vsk);
+ if (n_bytes < 0) {
+ ret = n_bytes;
+ break;
+ }
}
ret = put_user(n_bytes, arg);
diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
index 1e762a480df4..08baa1181ba6 100644
--- a/net/vmw_vsock/virtio_transport_common.c
+++ b/net/vmw_vsock/virtio_transport_common.c
@@ -204,6 +204,8 @@ static u16 virtio_transport_get_type(struct sock *sk)
{
if (sk->sk_type == SOCK_STREAM)
return VIRTIO_VSOCK_TYPE_STREAM;
+ else if (sk->sk_type == SOCK_DGRAM)
+ return VIRTIO_VSOCK_TYPE_DGRAM;
else
return VIRTIO_VSOCK_TYPE_SEQPACKET;
}
@@ -303,6 +305,7 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,
u32 src_cid, src_port, dst_cid, dst_port;
const struct virtio_transport *t_ops;
struct iov_iter_state msg_iter_state;
+ struct sock *sk = sk_vsock(vsk);
struct virtio_vsock_sock *vvs;
struct ubuf_info *uarg = NULL;
u32 pkt_len = info->pkt_len;
@@ -327,14 +330,27 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,
dst_port = info->remote_port;
}
- vvs = vsk->trans;
+ /* Datagram sends use socket memory accounting instead of peer credits. */
+ vvs = info->type == VIRTIO_VSOCK_TYPE_DGRAM ? NULL : vsk->trans;
- /* virtio_transport_get_credit might return less than pkt_len credit */
- pkt_len = virtio_transport_get_credit(vvs, pkt_len);
+ if (info->type == VIRTIO_VSOCK_TYPE_DGRAM) {
+ if (pkt_len > VIRTIO_VSOCK_MAX_PKT_BUF_SIZE)
+ return -EMSGSIZE;
- /* Do not send zero length OP_RW pkt */
- if (pkt_len == 0 && info->op == VIRTIO_VSOCK_OP_RW)
- return pkt_len;
+ /* Datagram sends are serialized by the socket lock. */
+ if (sk_wmem_alloc_get(sk) >= READ_ONCE(sk->sk_sndbuf)) {
+ sk_set_bit(SOCKWQ_ASYNC_NOSPACE, sk);
+ set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
+ return -EAGAIN;
+ }
+ } else {
+ /* virtio_transport_get_credit might return less than pkt_len credit */
+ pkt_len = virtio_transport_get_credit(vvs, pkt_len);
+
+ /* Do not send zero length OP_RW pkt */
+ if (pkt_len == 0 && info->op == VIRTIO_VSOCK_OP_RW)
+ return pkt_len;
+ }
if (info->msg && (info->msg->msg_flags & MSG_ZEROCOPY)) {
/* If 'info->msg' is not NULL, this is only VIRTIO_VSOCK_OP_RW.
@@ -348,7 +364,8 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,
uarg = msg_zerocopy_realloc(sk_vsock(vsk), pkt_len,
NULL, false);
if (!uarg) {
- virtio_transport_put_credit(vvs, pkt_len);
+ if (info->type != VIRTIO_VSOCK_TYPE_DGRAM)
+ virtio_transport_put_credit(vvs, pkt_len);
return -ENOMEM;
}
@@ -393,7 +410,8 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,
break;
}
- virtio_transport_inc_tx_pkt(vvs, skb);
+ if (info->type != VIRTIO_VSOCK_TYPE_DGRAM)
+ virtio_transport_inc_tx_pkt(vvs, skb);
ret = t_ops->send_pkt(skb, info->net);
if (ret < 0)
@@ -416,7 +434,8 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,
if (info->msg && ret < 0)
iov_iter_restore(&info->msg->msg_iter, &msg_iter_state);
- virtio_transport_put_credit(vvs, rest_len);
+ if (info->type != VIRTIO_VSOCK_TYPE_DGRAM)
+ virtio_transport_put_credit(vvs, rest_len);
/* msg_zerocopy_realloc() initializes the ubuf_info refcnt to 1.
* skb_zcopy_set() increases it for each skb, so we can drop that
@@ -620,7 +639,7 @@ void virtio_transport_consume_skb_sent(struct sk_buff *skb, bool consume)
{
struct sock *s = skb->sk;
- if (s && skb->len) {
+ if (s && skb->len && s->sk_type != SOCK_DGRAM) {
struct vsock_sock *vs = vsock_sk(s);
struct virtio_vsock_sock *vvs;
@@ -999,7 +1018,47 @@ virtio_transport_dgram_dequeue(struct vsock_sock *vsk,
struct msghdr *msg,
size_t len, int flags)
{
- return -EOPNOTSUPP;
+ struct sock *sk = sk_vsock(vsk);
+ struct virtio_vsock_hdr *hdr;
+ struct sk_buff *skb;
+ size_t payload_len;
+ int err;
+
+ if (flags & (MSG_OOB | MSG_ERRQUEUE))
+ return -EOPNOTSUPP;
+
+ skb = skb_recv_datagram(sk, flags, &err);
+ if (!skb)
+ return err;
+
+ hdr = virtio_vsock_hdr(skb);
+ payload_len = skb->len;
+
+ if (payload_len > len) {
+ payload_len = len;
+ msg->msg_flags |= MSG_TRUNC;
+ }
+
+ err = skb_copy_datagram_iter(skb, 0, &msg->msg_iter, payload_len);
+ if (err)
+ goto out;
+
+ if (msg->msg_name) {
+ DECLARE_SOCKADDR(struct sockaddr_vm *, vm_addr, msg->msg_name);
+
+ vsock_addr_init(vm_addr, le64_to_cpu(hdr->src_cid),
+ le32_to_cpu(hdr->src_port));
+ msg->msg_namelen = sizeof(*vm_addr);
+ }
+
+ if (flags & MSG_TRUNC)
+ err = skb->len;
+ else
+ err = payload_len;
+
+out:
+ skb_free_datagram(sk, skb);
+ return err;
}
EXPORT_SYMBOL_GPL(virtio_transport_dgram_dequeue);
@@ -1202,13 +1261,13 @@ EXPORT_SYMBOL_GPL(virtio_transport_stream_is_active);
int virtio_transport_dgram_bind(struct vsock_sock *vsk,
struct sockaddr_vm *addr)
{
- return -EOPNOTSUPP;
+ return vsock_bind_common(vsk, addr);
}
EXPORT_SYMBOL_GPL(virtio_transport_dgram_bind);
bool virtio_transport_dgram_allow(struct vsock_sock *vsk, u32 cid, u32 port)
{
- return false;
+ return true;
}
EXPORT_SYMBOL_GPL(virtio_transport_dgram_allow);
@@ -1246,7 +1305,17 @@ virtio_transport_dgram_enqueue(struct vsock_sock *vsk,
struct msghdr *msg,
size_t dgram_len)
{
- return -EOPNOTSUPP;
+ struct virtio_vsock_pkt_info info = {
+ .op = VIRTIO_VSOCK_OP_RW,
+ .msg = msg,
+ .pkt_len = dgram_len,
+ .vsk = vsk,
+ .remote_cid = remote_addr->svm_cid,
+ .remote_port = remote_addr->svm_port,
+ .net = sock_net(sk_vsock(vsk)),
+ };
+
+ return virtio_transport_send_pkt_info(vsk, &info);
}
EXPORT_SYMBOL_GPL(virtio_transport_dgram_enqueue);
@@ -1771,7 +1840,8 @@ virtio_transport_recv_listen(struct sock *sk, struct sk_buff *skb,
static bool virtio_transport_valid_type(u16 type)
{
return (type == VIRTIO_VSOCK_TYPE_STREAM) ||
- (type == VIRTIO_VSOCK_TYPE_SEQPACKET);
+ (type == VIRTIO_VSOCK_TYPE_SEQPACKET) ||
+ (type == VIRTIO_VSOCK_TYPE_DGRAM);
}
/* We are under the virtio-vsock's vsock->rx_lock or vhost-vsock's vq->mutex
@@ -1805,6 +1875,38 @@ void virtio_transport_recv_pkt(struct virtio_transport *t,
goto free_pkt;
}
+ /* Dgram packets are delivered directly to bound sockets without
+ * going through the connection state machine. Undeliverable
+ * datagrams are silently dropped (no RST).
+ */
+ if (le16_to_cpu(hdr->type) == VIRTIO_VSOCK_TYPE_DGRAM) {
+ if (le16_to_cpu(hdr->op) != VIRTIO_VSOCK_OP_RW)
+ goto free_pkt;
+
+ sk = vsock_find_bound_dgram_socket_net(&dst, net);
+
+ if (!sk)
+ goto free_pkt;
+
+ vsk = vsock_sk(sk);
+ lock_sock(sk);
+ if (sock_flag(sk, SOCK_DEAD) ||
+ vsk->transport != &t->transport ||
+ (sk->sk_socket->state != SS_UNCONNECTED &&
+ !vsock_check_source(vsk, &t->transport, &src))) {
+ release_sock(sk);
+ sock_put(sk);
+ goto free_pkt;
+ }
+
+ if (sock_queue_rcv_skb(sk, skb))
+ kfree_skb(skb);
+
+ release_sock(sk);
+ sock_put(sk);
+ return;
+ }
+
/* The socket must be in connected or bound table
* otherwise send reset back
*/
diff --git a/net/vmw_vsock/vsock_bpf.c b/net/vmw_vsock/vsock_bpf.c
index 9049d2648646..296e1fa7970b 100644
--- a/net/vmw_vsock/vsock_bpf.c
+++ b/net/vmw_vsock/vsock_bpf.c
@@ -161,7 +161,8 @@ int vsock_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore
if (!vsk->transport)
return -ENODEV;
- if (!vsk->transport->read_skb)
+ /* Datagram receive is not implemented by the sockmap callbacks. */
+ if (sk->sk_type == SOCK_DGRAM || !vsk->transport->read_skb)
return -EOPNOTSUPP;
vsock_bpf_check_needs_rebuild(psock->sk_proto);
--
2.56.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH RFC v2 4/8] vsock/virtio: negotiate datagram support on the guest
2026-11-13 12:47 [PATCH RFC v2 0/8] vsock: add datagram support to virtio and vhost Sergio Lopez
` (2 preceding siblings ...)
2026-11-13 12:47 ` [PATCH RFC v2 3/8] vsock/virtio: implement the datagram data path Sergio Lopez
@ 2026-11-13 12:47 ` Sergio Lopez
2026-11-13 12:47 ` [PATCH RFC v2 5/8] vhost/vsock: negotiate datagram support with guests Sergio Lopez
` (4 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Sergio Lopez @ 2026-11-13 12:47 UTC (permalink / raw)
To: Stefano Garzarella, Bobby Eshleman, Alexei Starovoitov,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Bryan Tan, Vishnu Dasa,
Broadcom internal kernel review list, Stefan Hajnoczi,
Michael S. Tsirkin, Jason Wang, Eugenio Pérez, Xuan Zhuo,
Haiyang Zhang, Wei Liu, Dexuan Cui, Shuah Khan, Randy Dunlap
Cc: virtualization, netdev, linux-kernel, kvm, bpf, linux-hyperv,
Jonathan Corbet, linux-doc, Sergio Lopez
Advertise VIRTIO_VSOCK_F_DGRAM and record whether the device
negotiated it. Gate datagram socket initialization and destination
checks on the negotiated feature and the transport's supported network
namespace mode.
Perform device capability checks at socket creation without requiring
a remote address, which is not yet known for an unconnected datagram
socket. The common virtio implementation provides the datagram data
path.
Discard received datagrams when the device has not negotiated support,
including when a socket was initialized through another transport.
Reject packets addressed to a different CID using the full 64-bit wire
value before bound-socket lookup narrows it or matches a wildcard.
Signed-off-by: Sergio Lopez <slp@redhat.com>
---
net/vmw_vsock/virtio_transport.c | 50 +++++++++++++++++++++++++++++++++++++---
1 file changed, 47 insertions(+), 3 deletions(-)
diff --git a/net/vmw_vsock/virtio_transport.c b/net/vmw_vsock/virtio_transport.c
index 4f9aa9c4c3aa..fd10f20d7611 100644
--- a/net/vmw_vsock/virtio_transport.c
+++ b/net/vmw_vsock/virtio_transport.c
@@ -57,6 +57,7 @@ struct virtio_vsock {
u32 guest_cid;
bool seqpacket_allow;
+ bool dgram_allow;
/* These fields are used only in tx path in function
* 'virtio_transport_send_pkt_work()', so to save
@@ -547,6 +548,18 @@ bool virtio_transport_stream_allow(struct vsock_sock *vsk, u32 cid, u32 port)
static bool virtio_transport_seqpacket_allow(struct vsock_sock *vsk,
u32 remote_cid);
+static bool virtio_transport_dgram_allow_check(struct vsock_sock *vsk,
+ u32 cid, u32 port);
+
+static int virtio_transport_socket_init(struct vsock_sock *vsk,
+ struct vsock_sock *psk)
+{
+ if (sk_vsock(vsk)->sk_type == SOCK_DGRAM &&
+ !virtio_transport_dgram_allow_check(vsk, 0, 0))
+ return -ESOCKTNOSUPPORT;
+
+ return virtio_transport_do_socket_init(vsk, psk);
+}
static bool virtio_transport_has_remote_cid(struct vsock_sock *vsk, u32 cid)
{
@@ -561,7 +574,7 @@ static struct virtio_transport virtio_transport = {
.get_local_cid = virtio_transport_get_local_cid,
.has_remote_cid = virtio_transport_has_remote_cid,
- .init = virtio_transport_do_socket_init,
+ .init = virtio_transport_socket_init,
.destruct = virtio_transport_destruct,
.release = virtio_transport_release,
.connect = virtio_transport_connect,
@@ -571,7 +584,7 @@ static struct virtio_transport virtio_transport = {
.dgram_bind = virtio_transport_dgram_bind,
.dgram_dequeue = virtio_transport_dgram_dequeue,
.dgram_enqueue = virtio_transport_dgram_enqueue,
- .dgram_allow = virtio_transport_dgram_allow,
+ .dgram_allow = virtio_transport_dgram_allow_check,
.stream_dequeue = virtio_transport_stream_dequeue,
.stream_enqueue = virtio_transport_stream_enqueue,
@@ -629,6 +642,25 @@ virtio_transport_seqpacket_allow(struct vsock_sock *vsk, u32 remote_cid)
return seqpacket_allow;
}
+static bool
+virtio_transport_dgram_allow_check(struct vsock_sock *vsk, u32 cid, u32 port)
+{
+ struct virtio_vsock *vsock;
+ bool dgram_allow;
+
+ if (!vsock_net_mode_global(vsk))
+ return false;
+
+ dgram_allow = false;
+ rcu_read_lock();
+ vsock = rcu_dereference(the_virtio_vsock);
+ if (vsock)
+ dgram_allow = vsock->dgram_allow;
+ rcu_read_unlock();
+
+ return dgram_allow;
+}
+
static void virtio_transport_rx_work(struct work_struct *work)
{
struct virtio_vsock *vsock =
@@ -677,6 +709,14 @@ static void virtio_transport_rx_work(struct work_struct *work)
continue;
}
+ /* Check the full wire CID before socket lookup truncates it. */
+ if (le16_to_cpu(hdr->type) == VIRTIO_VSOCK_TYPE_DGRAM &&
+ (!vsock->dgram_allow ||
+ le64_to_cpu(hdr->dst_cid) != vsock->guest_cid)) {
+ kfree_skb(skb);
+ continue;
+ }
+
if (payload_len)
virtio_vsock_skb_put(skb, payload_len);
@@ -835,6 +875,9 @@ static int virtio_vsock_probe(struct virtio_device *vdev)
if (virtio_has_feature(vdev, VIRTIO_VSOCK_F_SEQPACKET))
vsock->seqpacket_allow = true;
+ if (virtio_has_feature(vdev, VIRTIO_VSOCK_F_DGRAM))
+ vsock->dgram_allow = true;
+
vdev->priv = vsock;
ret = virtio_vsock_vqs_init(vsock);
@@ -932,7 +975,8 @@ static struct virtio_device_id id_table[] = {
};
static unsigned int features[] = {
- VIRTIO_VSOCK_F_SEQPACKET
+ VIRTIO_VSOCK_F_SEQPACKET,
+ VIRTIO_VSOCK_F_DGRAM,
};
static struct virtio_driver virtio_vsock_driver = {
--
2.56.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH RFC v2 5/8] vhost/vsock: negotiate datagram support with guests
2026-11-13 12:47 [PATCH RFC v2 0/8] vsock: add datagram support to virtio and vhost Sergio Lopez
` (3 preceding siblings ...)
2026-11-13 12:47 ` [PATCH RFC v2 4/8] vsock/virtio: negotiate datagram support on the guest Sergio Lopez
@ 2026-11-13 12:47 ` Sergio Lopez
2026-11-13 12:47 ` [PATCH RFC v2 6/8] vsock: allow datagrams to use directional transports Sergio Lopez
` (3 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Sergio Lopez @ 2026-11-13 12:47 UTC (permalink / raw)
To: Stefano Garzarella, Bobby Eshleman, Alexei Starovoitov,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Bryan Tan, Vishnu Dasa,
Broadcom internal kernel review list, Stefan Hajnoczi,
Michael S. Tsirkin, Jason Wang, Eugenio Pérez, Xuan Zhuo,
Haiyang Zhang, Wei Liu, Dexuan Cui, Shuah Khan, Randy Dunlap
Cc: virtualization, netdev, linux-kernel, kvm, bpf, linux-hyperv,
Jonathan Corbet, linux-doc, Sergio Lopez
Expose VIRTIO_VSOCK_F_DGRAM in the vhost feature bitmap and retain the
negotiated value for each guest. Keep datagrams disabled until the
feature is negotiated.
Use a vhost-specific datagram destination check to look up the guest
in the socket's network namespace and require its negotiated datagram
feature. The common virtio implementation supplies the bind and
message operations; destination validation remains in connect() and
sendmsg(), where the remote CID is available.
Reject guest datagrams on ingress unless the receiving virtqueue
acknowledged the feature. Complete rejected descriptors normally so the
queue can continue processing subsequent traffic.
Signed-off-by: Sergio Lopez <slp@redhat.com>
---
drivers/vhost/vsock.c | 34 ++++++++++++++++++++++++++++++----
1 file changed, 30 insertions(+), 4 deletions(-)
diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c
index 0cb87971d749..7971f6f0b6a2 100644
--- a/drivers/vhost/vsock.c
+++ b/drivers/vhost/vsock.c
@@ -32,7 +32,8 @@
static const int vhost_vsock_bits[] = {
VHOST_FEATURES,
VIRTIO_F_ACCESS_PLATFORM,
- VIRTIO_VSOCK_F_SEQPACKET
+ VIRTIO_VSOCK_F_SEQPACKET,
+ VIRTIO_VSOCK_F_DGRAM,
};
#define VHOST_VSOCK_FEATURES VHOST_FEATURES_U64(vhost_vsock_bits, 0)
@@ -62,6 +63,7 @@ struct vhost_vsock {
u32 guest_cid;
bool seqpacket_allow;
bool ever_started; /* set on first SET_RUNNING(1); never cleared */
+ bool dgram_allow;
};
static u32 vhost_transport_get_local_cid(void)
@@ -436,6 +438,8 @@ static bool vhost_transport_msgzerocopy_allow(void)
static bool vhost_transport_seqpacket_allow(struct vsock_sock *vsk,
u32 remote_cid);
+static bool vhost_transport_dgram_allow(struct vsock_sock *vsk,
+ u32 cid, u32 port);
static bool
vhost_transport_stream_allow(struct vsock_sock *vsk, u32 cid, u32 port)
@@ -460,7 +464,7 @@ static struct virtio_transport vhost_transport = {
.dgram_enqueue = virtio_transport_dgram_enqueue,
.dgram_dequeue = virtio_transport_dgram_dequeue,
.dgram_bind = virtio_transport_dgram_bind,
- .dgram_allow = virtio_transport_dgram_allow,
+ .dgram_allow = vhost_transport_dgram_allow,
.stream_enqueue = virtio_transport_stream_enqueue,
.stream_dequeue = virtio_transport_stream_dequeue,
@@ -516,6 +520,24 @@ static bool vhost_transport_seqpacket_allow(struct vsock_sock *vsk,
return seqpacket_allow;
}
+static bool vhost_transport_dgram_allow(struct vsock_sock *vsk,
+ u32 cid, u32 port)
+{
+ struct net *net = sock_net(sk_vsock(vsk));
+ struct vhost_vsock *vsock;
+ bool dgram_allow = false;
+
+ rcu_read_lock();
+ vsock = vhost_vsock_get(cid, net);
+
+ if (vsock)
+ dgram_allow = vsock->dgram_allow;
+
+ rcu_read_unlock();
+
+ return dgram_allow;
+}
+
static void vhost_vsock_handle_tx_kick(struct vhost_work *work)
{
struct vhost_virtqueue *vq = container_of(work, struct vhost_virtqueue,
@@ -573,10 +595,12 @@ static void vhost_vsock_handle_tx_kick(struct vhost_work *work)
hdr = virtio_vsock_hdr(skb);
- /* Only accept correctly addressed packets */
+ /* Check addressing and datagram feature negotiation. */
if (le64_to_cpu(hdr->src_cid) == vsock->guest_cid &&
le64_to_cpu(hdr->dst_cid) ==
- vhost_transport_get_local_cid())
+ vhost_transport_get_local_cid() &&
+ (le16_to_cpu(hdr->type) != VIRTIO_VSOCK_TYPE_DGRAM ||
+ vhost_has_feature(vq, VIRTIO_VSOCK_F_DGRAM)))
virtio_transport_recv_pkt(&vhost_transport, skb,
vsock->net);
else
@@ -737,6 +761,7 @@ static int vhost_vsock_dev_open(struct inode *inode, struct file *file)
vsock->guest_cid = 0; /* no CID assigned yet */
vsock->seqpacket_allow = false;
vsock->ever_started = false;
+ vsock->dgram_allow = false;
atomic_set(&vsock->queued_replies, 0);
@@ -890,6 +915,7 @@ static int vhost_vsock_set_features(struct vhost_vsock *vsock, u64 features)
}
vsock->seqpacket_allow = features & (1ULL << VIRTIO_VSOCK_F_SEQPACKET);
+ vsock->dgram_allow = features & (1ULL << VIRTIO_VSOCK_F_DGRAM);
for (i = 0; i < ARRAY_SIZE(vsock->vqs); i++) {
vq = &vsock->vqs[i];
--
2.56.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH RFC v2 6/8] vsock: allow datagrams to use directional transports
2026-11-13 12:47 [PATCH RFC v2 0/8] vsock: add datagram support to virtio and vhost Sergio Lopez
` (4 preceding siblings ...)
2026-11-13 12:47 ` [PATCH RFC v2 5/8] vhost/vsock: negotiate datagram support with guests Sergio Lopez
@ 2026-11-13 12:47 ` Sergio Lopez
2026-11-13 13:39 ` sashiko-bot
2026-11-13 12:47 ` [PATCH RFC v2 7/8] vsock: fragment datagrams across receive buffers Sergio Lopez
` (2 subsequent siblings)
8 siblings, 1 reply; 13+ messages in thread
From: Sergio Lopez @ 2026-11-13 12:47 UTC (permalink / raw)
To: Stefano Garzarella, Bobby Eshleman, Alexei Starovoitov,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Bryan Tan, Vishnu Dasa,
Broadcom internal kernel review list, Stefan Hajnoczi,
Michael S. Tsirkin, Jason Wang, Eugenio Pérez, Xuan Zhuo,
Haiyang Zhang, Wei Liu, Dexuan Cui, Shuah Khan, Randy Dunlap
Cc: virtualization, netdev, linux-kernel, kvm, bpf, linux-hyperv,
Jonathan Corbet, linux-doc, Sergio Lopez
Use the datagram fallback transport to own sockets that need transport-
specific bind state. VMCI needs this ownership to create and destroy its
receive handle. Without a fallback, initialize socket state through a
directional transport or loopback; use loopback if a directional transport
does not support datagrams. Hyper-V still rejects datagrams.
Share local, guest-to-host, host-to-guest, and parent-fallback route
selection with stream and seqpacket sockets. Choose a capable directional
transport for each datagram destination and use VMCI when none can serve
the peer. Keep VMCI as the socket owner across connect and send calls so
changing destinations cannot destroy its receive handle. Preserve parent
routing in returned addresses and when connecting through the guest-to-host
fallback. A host CID remains a loopback alias when the guest transport has
no active device.
Reserve VMCI-bound ports in the core table as well as in VMCI so virtio
and vhost can deliver to the same socket. Undo the core bind if VMCI handle
creation fails, and retry ephemeral ports that collide with VMCI resources.
Queue only datagram payloads with common source metadata in both receive
paths. Read the shared queue directly in core recvmsg() and report payload
size through SIOCINQ. Preserve the virtio TO_HOST source flag, the VMCI
source CID and port, peeking, truncation, and socket filtering.
Allow virtio datagram sends from a VMCI-owned socket without interpreting
VMCI private state as virtio state. The skb's socket ownership tracks send
memory regardless of which transport owns the socket. Per-send capability
checks still reject host traffic when virtio datagrams were not negotiated.
Check connected sources against the peer's selected route and exact CID
and port, including on loopback. Wildcard loopback senders use the
addressed local CID; explicit bindings retain their own CID. Reject
loopback source bindings whose reply address selects another transport.
Use a copy of the peer address during connected sends so a temporary
parent fallback does not pin later sends to the host. Remove sockets from
the core tables when transport initialization fails.
Paired stream, seqpacket, and datagram tests pass with virtio/vhost.
Datagram tests also pass with a temporary VMCI-like fallback owner. The
fallback tests cover mixed send and receive, VMCI-style bind rollback, and
an unclaimed destination. A child-disappearance reproducer confirms parent
fallback and direct-route recovery. VMCI and Hyper-V objects build; real
VMCI hardware has not been exercised.
Link: https://lore.kernel.org/all/DMGNAV67LK3F.2FNLTD5NVHDEA@gmail.com/
Signed-off-by: Sergio Lopez <slp@redhat.com>
---
Documentation/admin-guide/sysctl/net.rst | 8 +-
drivers/vhost/vsock.c | 2 +-
include/linux/virtio_vsock.h | 9 +-
include/net/af_vsock.h | 29 ++-
net/vmw_vsock/af_vsock.c | 306 ++++++++++++++++++++++++++-----
net/vmw_vsock/hyperv_transport.c | 11 +-
net/vmw_vsock/virtio_transport.c | 2 +-
net/vmw_vsock/virtio_transport_common.c | 113 ++++++------
net/vmw_vsock/vmci_transport.c | 124 ++++---------
net/vmw_vsock/vsock_loopback.c | 2 +-
10 files changed, 390 insertions(+), 216 deletions(-)
diff --git a/Documentation/admin-guide/sysctl/net.rst b/Documentation/admin-guide/sysctl/net.rst
index fe43e8595958..30d81128fd29 100644
--- a/Documentation/admin-guide/sysctl/net.rst
+++ b/Documentation/admin-guide/sysctl/net.rst
@@ -586,9 +586,11 @@ reachable through the hypervisor (e.g. Nitro Enclaves) to address both using
a single CID space, without requiring applications to set
``VMADDR_FLAG_TO_HOST``.
-When the fallback is taken, ``VMADDR_FLAG_TO_HOST`` is automatically set on
-the remote address so that userspace can determine the path via
-``getpeername()``.
+When a connect falls back, ``VMADDR_FLAG_TO_HOST`` is automatically set on
+the connected peer address so that userspace can determine the selected path
+via ``getpeername()``. A datagram send on an existing connection can also fall
+back; in that case the flag is set only on the per-send destination. Later
+sends can use H2G again if it takes ownership of the destination CID.
Note: With this sysctl enabled, user space that attempts to talk to a guest
CID which is not implemented by the H2G transport will create host vsock
diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c
index 7971f6f0b6a2..f4fb2ac732f5 100644
--- a/drivers/vhost/vsock.c
+++ b/drivers/vhost/vsock.c
@@ -462,9 +462,9 @@ static struct virtio_transport vhost_transport = {
.cancel_pkt = vhost_transport_cancel_pkt,
.dgram_enqueue = virtio_transport_dgram_enqueue,
- .dgram_dequeue = virtio_transport_dgram_dequeue,
.dgram_bind = virtio_transport_dgram_bind,
.dgram_allow = vhost_transport_dgram_allow,
+ .dgram_transport_match = virtio_transport_dgram_transport_match,
.stream_enqueue = virtio_transport_stream_enqueue,
.stream_dequeue = virtio_transport_stream_dequeue,
diff --git a/include/linux/virtio_vsock.h b/include/linux/virtio_vsock.h
index f91704731057..16b869085a0a 100644
--- a/include/linux/virtio_vsock.h
+++ b/include/linux/virtio_vsock.h
@@ -12,6 +12,7 @@
struct virtio_vsock_skb_cb {
bool reply;
bool tap_delivered;
+ bool to_host;
u32 offset;
};
@@ -171,6 +172,7 @@ struct virtio_vsock_sock {
struct virtio_vsock_pkt_info {
u32 remote_cid, remote_port;
+ const struct virtio_transport *t_ops;
struct vsock_sock *vsk;
struct msghdr *msg;
struct net *net;
@@ -204,11 +206,6 @@ virtio_transport_stream_dequeue(struct vsock_sock *vsk,
size_t len,
int type);
int
-virtio_transport_dgram_dequeue(struct vsock_sock *vsk,
- struct msghdr *msg,
- size_t len, int flags);
-
-int
virtio_transport_seqpacket_enqueue(struct vsock_sock *vsk,
struct msghdr *msg,
size_t len);
@@ -261,6 +258,7 @@ bool virtio_transport_stream_allow(struct vsock_sock *vsk, u32 cid, u32 port);
int virtio_transport_dgram_bind(struct vsock_sock *vsk,
struct sockaddr_vm *addr);
bool virtio_transport_dgram_allow(struct vsock_sock *vsk, u32 cid, u32 port);
+bool virtio_transport_dgram_transport_match(const struct vsock_transport *t);
int virtio_transport_connect(struct vsock_sock *vsk);
@@ -274,6 +272,7 @@ virtio_transport_stream_enqueue(struct vsock_sock *vsk,
size_t len);
int
virtio_transport_dgram_enqueue(struct vsock_sock *vsk,
+ const struct vsock_transport *transport,
struct sockaddr_vm *remote_addr,
struct msghdr *msg,
size_t len);
diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h
index e4fbea3b0e46..a62e8076ee29 100644
--- a/include/net/af_vsock.h
+++ b/include/net/af_vsock.h
@@ -26,6 +26,13 @@ extern spinlock_t vsock_table_lock;
#define vsock_sk(__sk) ((struct vsock_sock *)__sk)
#define sk_vsock(__vsk) (&(__vsk)->sk)
+/* Datagrams from different transports share the socket receive queue. */
+struct vsock_dgram_skb_cb {
+ struct sockaddr_vm src;
+};
+
+#define VSOCK_DGRAM_SKB_CB(skb) ((struct vsock_dgram_skb_cb *)((skb)->cb))
+
struct vsock_sock {
/* sk must be the first member. */
struct sock sk;
@@ -98,8 +105,8 @@ struct vsock_transport_send_notify_data {
#define VSOCK_TRANSPORT_F_H2G 0x00000001
/* Transport provides guest->host communication */
#define VSOCK_TRANSPORT_F_G2H 0x00000002
-/* Transport provides DGRAM communication */
-#define VSOCK_TRANSPORT_F_DGRAM 0x00000004
+/* Transport provides fallback DGRAM communication and owns its endpoint */
+#define VSOCK_TRANSPORT_F_DGRAM_FALLBACK 0x00000004
/* Transport provides local (loopback) communication */
#define VSOCK_TRANSPORT_F_LOCAL 0x00000008
@@ -119,13 +126,15 @@ struct vsock_transport {
/* DGRAM. */
int (*dgram_bind)(struct vsock_sock *, struct sockaddr_vm *);
- int (*dgram_dequeue)(struct vsock_sock *vsk, struct msghdr *msg,
- size_t len, int flags);
- int (*dgram_enqueue)(struct vsock_sock *, struct sockaddr_vm *,
- struct msghdr *, size_t len);
+ int (*dgram_enqueue)(struct vsock_sock *vsk,
+ const struct vsock_transport *transport,
+ struct sockaddr_vm *remote_addr,
+ struct msghdr *msg, size_t len);
bool (*dgram_allow)(struct vsock_sock *vsk, u32 cid, u32 port);
- /* Optional payload length override, called with the receive queue locked. */
- size_t (*dgram_payload_len)(const struct sk_buff *skb);
+ /* A fallback transport can share its datagram sockets with routed ones. */
+ bool dgram_fallback;
+ /* Optional: another transport can use this socket's datagram state. */
+ bool (*dgram_transport_match)(const struct vsock_transport *transport);
/* STREAM. */
/* TODO: stream_bind() */
@@ -223,6 +232,7 @@ void vsock_enqueue_accept(struct sock *listener, struct sock *connected);
void vsock_pending_to_accept(struct sock *listener, struct sock *pending);
void vsock_insert_connected(struct vsock_sock *vsk);
int vsock_bind_common(struct vsock_sock *vsk, struct sockaddr_vm *addr);
+void vsock_unbind_common(struct vsock_sock *vsk);
void vsock_remove_bound(struct vsock_sock *vsk);
void vsock_remove_connected(struct vsock_sock *vsk);
struct sock *vsock_find_bound_socket(struct sockaddr_vm *addr);
@@ -242,6 +252,9 @@ void vsock_remove_sock(struct vsock_sock *vsk);
void vsock_for_each_connected_socket(struct vsock_transport *transport,
void (*fn)(struct sock *sk));
int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk);
+const struct vsock_transport *
+vsock_dgram_get_transport(struct vsock_sock *vsk,
+ struct sockaddr_vm *remote_addr);
bool vsock_find_cid(unsigned int cid);
void vsock_linger(struct sock *sk);
diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
index aef5389eeca2..7e52c7e5583c 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -132,6 +132,7 @@
#include <linux/types.h>
#include <linux/bitops.h>
#include <linux/cred.h>
+#include <linux/err.h>
#include <linux/errqueue.h>
#include <linux/init.h>
#include <linux/io.h>
@@ -198,8 +199,8 @@ struct proto vsock_proto = {
static const struct vsock_transport *transport_h2g;
/* Transport used for guest->host communication */
static const struct vsock_transport *transport_g2h;
-/* Transport used for DGRAM communication */
-static const struct vsock_transport *transport_dgram;
+/* Transport owning fallback datagram endpoints */
+static const struct vsock_transport *transport_dgram_fallback;
/* Transport used for local communication */
static const struct vsock_transport *transport_local;
static DEFINE_MUTEX(vsock_register_mutex);
@@ -568,6 +569,8 @@ EXPORT_SYMBOL_GPL(vsock_enqueue_accept);
static bool vsock_use_local_transport(unsigned int remote_cid)
{
+ u32 local_cid;
+
lockdep_assert_held(&vsock_register_mutex);
if (!transport_local)
@@ -576,11 +579,50 @@ static bool vsock_use_local_transport(unsigned int remote_cid)
if (remote_cid == VMADDR_CID_LOCAL)
return true;
- if (transport_g2h) {
- return remote_cid == transport_g2h->get_local_cid();
- } else {
- return remote_cid == VMADDR_CID_HOST;
- }
+ local_cid = transport_g2h ? transport_g2h->get_local_cid() :
+ VMADDR_CID_ANY;
+ if (local_cid == VMADDR_CID_ANY)
+ local_cid = VMADDR_CID_HOST;
+
+ return remote_cid == local_cid;
+}
+
+/* Select a route without changing socket ownership. The caller holds
+ * vsock_register_mutex and pins the returned transport before releasing it.
+ */
+static const struct vsock_transport *
+vsock_select_transport(struct vsock_sock *vsk,
+ const struct sockaddr_vm *remote_addr,
+ const struct vsock_transport *dgram_owner,
+ bool *to_host)
+{
+ struct sock *sk = sk_vsock(vsk);
+ u32 cid = remote_addr->svm_cid;
+
+ lockdep_assert_held(&vsock_register_mutex);
+ *to_host = false;
+
+ if (vsock_use_local_transport(cid))
+ return transport_local;
+ /* VMCI may use a different local CID from the virtio transports. */
+ if (dgram_owner && dgram_owner->dgram_fallback &&
+ cid == dgram_owner->get_local_cid())
+ return dgram_owner;
+ if (cid <= VMADDR_CID_HOST ||
+ (remote_addr->svm_flags & VMADDR_FLAG_TO_HOST))
+ return transport_g2h;
+ if (transport_h2g &&
+ (!transport_h2g->has_remote_cid ||
+ transport_h2g->has_remote_cid(vsk, cid)))
+ return transport_h2g;
+ if (sock_net(sk)->vsock.g2h_fallback && transport_g2h &&
+ transport_g2h->has_remote_cid &&
+ transport_g2h->has_remote_cid(vsk, cid)) {
+ *to_host = true;
+ return transport_g2h;
+ }
+
+ return transport_h2g;
}
static void vsock_deassign_transport(struct vsock_sock *vsk)
@@ -612,9 +654,11 @@ static void vsock_deassign_transport(struct vsock_sock *vsk)
int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)
{
const struct vsock_transport *new_transport;
+ const struct vsock_transport *fallback_transport = NULL;
+ const struct vsock_transport *local_fallback = NULL;
struct sock *sk = sk_vsock(vsk);
unsigned int remote_cid = vsk->remote_addr.svm_cid;
- __u8 remote_flags;
+ bool to_host;
int ret;
/* If the packet is coming with the source and destination CIDs higher
@@ -629,33 +673,28 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)
vsk->remote_addr.svm_cid > VMADDR_CID_HOST)
vsk->remote_addr.svm_flags |= VMADDR_FLAG_TO_HOST;
- remote_flags = vsk->remote_addr.svm_flags;
-
mutex_lock(&vsock_register_mutex);
switch (sk->sk_type) {
case SOCK_DGRAM:
- new_transport = transport_dgram;
+ /* VMCI must own bind() to create its receive handle, even when
+ * directional transports serve individual destinations.
+ */
+ if (transport_dgram_fallback)
+ new_transport = transport_dgram_fallback;
+ else if (transport_g2h)
+ new_transport = transport_g2h;
+ else if (transport_h2g)
+ new_transport = transport_h2g;
+ else
+ new_transport = transport_local;
break;
case SOCK_STREAM:
case SOCK_SEQPACKET:
- if (vsock_use_local_transport(remote_cid))
- new_transport = transport_local;
- else if (remote_cid <= VMADDR_CID_HOST ||
- (remote_flags & VMADDR_FLAG_TO_HOST))
- new_transport = transport_g2h;
- else if (transport_h2g &&
- (!transport_h2g->has_remote_cid ||
- transport_h2g->has_remote_cid(vsk, remote_cid)))
- new_transport = transport_h2g;
- else if (sock_net(sk)->vsock.g2h_fallback &&
- transport_g2h && transport_g2h->has_remote_cid &&
- transport_g2h->has_remote_cid(vsk, remote_cid)) {
+ new_transport = vsock_select_transport(vsk, &vsk->remote_addr,
+ NULL, &to_host);
+ if (to_host)
vsk->remote_addr.svm_flags |= VMADDR_FLAG_TO_HOST;
- new_transport = transport_g2h;
- } else {
- new_transport = transport_h2g;
- }
break;
default:
ret = -ESOCKTNOSUPPORT;
@@ -674,6 +713,17 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)
ret = -ENODEV;
goto err;
}
+ if (sk->sk_type == SOCK_DGRAM && !transport_dgram_fallback &&
+ new_transport == transport_g2h && transport_h2g &&
+ transport_h2g != new_transport &&
+ try_module_get(transport_h2g->module))
+ fallback_transport = transport_h2g;
+ /* Loopback can own datagram state when directional transports cannot. */
+ if (sk->sk_type == SOCK_DGRAM && !transport_dgram_fallback &&
+ new_transport != transport_local && transport_local &&
+ transport_local != fallback_transport &&
+ try_module_get(transport_local->module))
+ local_fallback = transport_local;
/* It's safe to release the mutex after a successful try_module_get().
* Whichever transport `new_transport` points at, it won't go away until
@@ -710,6 +760,22 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)
}
ret = new_transport->init(vsk, psk);
+ if (ret == -ESOCKTNOSUPPORT && fallback_transport) {
+ module_put(new_transport->module);
+ new_transport = fallback_transport;
+ fallback_transport = NULL;
+ ret = new_transport->init(vsk, psk);
+ }
+ if (ret == -ESOCKTNOSUPPORT && local_fallback) {
+ module_put(new_transport->module);
+ new_transport = local_fallback;
+ local_fallback = NULL;
+ ret = new_transport->init(vsk, psk);
+ }
+ if (fallback_transport)
+ module_put(fallback_transport->module);
+ if (local_fallback)
+ module_put(local_fallback->module);
if (ret) {
module_put(new_transport->module);
return ret;
@@ -724,6 +790,101 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)
}
EXPORT_SYMBOL_GPL(vsock_assign_transport);
+/**
+ * vsock_dgram_get_transport - select a transport for a datagram destination
+ * @vsk: socket whose datagram state will be used
+ * @remote_addr: destination address
+ *
+ * The caller must hold the socket lock and release the returned transport's
+ * module reference after use. A datagram fallback owns its socket's binding,
+ * but a directional transport takes precedence when it can reach the peer.
+ * Parent fallback sets VMADDR_FLAG_TO_HOST to preserve that route on connect.
+ *
+ * Return: a referenced transport or an ERR_PTR on failure.
+ */
+const struct vsock_transport *
+vsock_dgram_get_transport(struct vsock_sock *vsk,
+ struct sockaddr_vm *remote_addr)
+{
+ const struct vsock_transport *owner = vsk->transport;
+ const struct vsock_transport *transport;
+ u32 cid = remote_addr->svm_cid;
+ u32 local_cid;
+ bool to_host = false;
+ bool shared;
+ int err = -ENODEV;
+
+ if (!owner)
+ return ERR_PTR(-ENODEV);
+
+ mutex_lock(&vsock_register_mutex);
+ shared = owner->dgram_transport_match || owner->dgram_fallback;
+ if (!shared)
+ transport = owner;
+ else
+ transport = vsock_select_transport(vsk, remote_addr, owner,
+ &to_host);
+
+ if (owner->dgram_fallback && transport != owner &&
+ (!transport || !transport->dgram_transport_match ||
+ !transport->dgram_transport_match(transport) ||
+ !transport->dgram_allow ||
+ !transport->dgram_allow(vsk, cid, remote_addr->svm_port))) {
+ transport = owner;
+ to_host = false;
+ }
+ if (owner->dgram_fallback && transport != owner &&
+ transport->get_local_cid() != VMADDR_CID_LOCAL &&
+ vsk->local_addr.svm_cid != VMADDR_CID_ANY &&
+ vsk->local_addr.svm_cid != transport->get_local_cid()) {
+ transport = owner;
+ to_host = false;
+ }
+
+ if (!transport)
+ goto out;
+ if (owner->dgram_transport_match &&
+ !owner->dgram_transport_match(transport)) {
+ err = -ESOCKTNOSUPPORT;
+ goto out;
+ }
+ /* Loopback source addresses must route replies back to loopback. */
+ if (shared && transport == transport_local &&
+ vsk->local_addr.svm_cid != VMADDR_CID_ANY &&
+ !vsock_use_local_transport(vsk->local_addr.svm_cid)) {
+ err = -EADDRNOTAVAIL;
+ goto out;
+ }
+ if (!try_module_get(transport->module))
+ goto out;
+ mutex_unlock(&vsock_register_mutex);
+
+ if (shared) {
+ local_cid = transport->get_local_cid();
+ if (vsk->local_addr.svm_cid != VMADDR_CID_ANY &&
+ local_cid != VMADDR_CID_LOCAL &&
+ vsk->local_addr.svm_cid != local_cid) {
+ err = -EADDRNOTAVAIL;
+ goto put_transport;
+ }
+ }
+ if (!transport->dgram_allow(vsk, cid, remote_addr->svm_port)) {
+ err = -EINVAL;
+ goto put_transport;
+ }
+ if (to_host)
+ remote_addr->svm_flags |= VMADDR_FLAG_TO_HOST;
+ return transport;
+
+put_transport:
+ module_put(transport->module);
+ return ERR_PTR(err);
+out:
+ mutex_unlock(&vsock_register_mutex);
+ return ERR_PTR(err);
+}
+EXPORT_SYMBOL_GPL(vsock_dgram_get_transport);
+
/*
* Provide safe access to static transport_{h2g,g2h,dgram,local} callbacks.
* Otherwise we may race with module removal. Do not use on `vsk->transport`.
@@ -924,6 +1085,17 @@ int vsock_bind_common(struct vsock_sock *vsk, struct sockaddr_vm *addr)
}
EXPORT_SYMBOL_GPL(vsock_bind_common);
+/* Undo a transport binding that could not be completed after reserving a port. */
+void vsock_unbind_common(struct vsock_sock *vsk)
+{
+ spin_lock_bh(&vsock_table_lock);
+ __vsock_remove_bound(vsk);
+ vsock_addr_unbind(&vsk->local_addr);
+ __vsock_insert_bound(vsock_unbound_sockets, vsk);
+ spin_unlock_bh(&vsock_table_lock);
+}
+EXPORT_SYMBOL_GPL(vsock_unbind_common);
+
static int __vsock_bind(struct sock *sk, struct sockaddr_vm *addr)
{
struct vsock_sock *vsk = vsock_sk(sk);
@@ -1055,7 +1227,7 @@ static void __vsock_release(struct sock *sk, int level)
if (vsk->transport)
vsk->transport->release(vsk);
- else if (sock_type_connectible(sk->sk_type))
+ else
vsock_remove_sock(vsk);
sock_orphan(sk);
@@ -1468,6 +1640,7 @@ static int vsock_dgram_sendmsg(struct socket *sock, struct msghdr *msg,
struct sock *sk;
struct vsock_sock *vsk;
struct sockaddr_vm *remote_addr;
+ struct sockaddr_vm connected_addr;
const struct vsock_transport *transport;
if (msg->msg_flags & MSG_OOB)
@@ -1498,7 +1671,8 @@ static int vsock_dgram_sendmsg(struct socket *sock, struct msghdr *msg,
*/
if (remote_addr->svm_cid == VMADDR_CID_ANY)
- remote_addr->svm_cid = transport->get_local_cid();
+ remote_addr->svm_cid = transport->dgram_transport_match ?
+ VMADDR_CID_LOCAL : transport->get_local_cid();
if (!vsock_addr_bound(remote_addr)) {
err = -EINVAL;
@@ -1508,27 +1682,32 @@ static int vsock_dgram_sendmsg(struct socket *sock, struct msghdr *msg,
remote_addr = &vsk->remote_addr;
if (remote_addr->svm_cid == VMADDR_CID_ANY)
- remote_addr->svm_cid = transport->get_local_cid();
+ remote_addr->svm_cid = transport->dgram_transport_match ?
+ VMADDR_CID_LOCAL : transport->get_local_cid();
/* XXX Should connect() or this function ensure remote_addr is
* bound?
*/
- if (!vsock_addr_bound(&vsk->remote_addr)) {
+ if (!vsock_addr_bound(remote_addr)) {
err = -EINVAL;
goto out;
}
+ /* A fallback must not pin subsequent sends to the host. */
+ connected_addr = *remote_addr;
+ remote_addr = &connected_addr;
} else {
err = -EINVAL;
goto out;
}
- if (!transport->dgram_allow(vsk, remote_addr->svm_cid,
- remote_addr->svm_port)) {
- err = -EINVAL;
+ transport = vsock_dgram_get_transport(vsk, remote_addr);
+ if (IS_ERR(transport)) {
+ err = PTR_ERR(transport);
goto out;
}
- err = transport->dgram_enqueue(vsk, remote_addr, msg, len);
+ err = transport->dgram_enqueue(vsk, transport, remote_addr, msg, len);
+ module_put(transport->module);
out:
release_sock(sk);
@@ -1542,6 +1721,7 @@ static int vsock_dgram_connect(struct socket *sock,
struct sock *sk;
struct vsock_sock *vsk;
struct sockaddr_vm *remote_addr;
+ const struct vsock_transport *transport;
sk = sock->sk;
vsk = vsock_sk(sk);
@@ -1563,11 +1743,16 @@ static int vsock_dgram_connect(struct socket *sock,
if (err)
goto out;
- if (!vsk->transport->dgram_allow(vsk, remote_addr->svm_cid,
- remote_addr->svm_port)) {
- err = -EINVAL;
+ if (remote_addr->svm_cid == VMADDR_CID_ANY &&
+ vsk->transport->dgram_transport_match)
+ remote_addr->svm_cid = VMADDR_CID_LOCAL;
+
+ transport = vsock_dgram_get_transport(vsk, remote_addr);
+ if (IS_ERR(transport)) {
+ err = PTR_ERR(transport);
goto out;
}
+ module_put(transport->module);
memcpy(&vsk->remote_addr, remote_addr, sizeof(vsk->remote_addr));
sock->state = SS_CONNECTED;
@@ -1591,9 +1776,35 @@ int __vsock_dgram_recvmsg(struct socket *sock, struct msghdr *msg,
size_t len, int flags)
{
struct sock *sk = sock->sk;
- struct vsock_sock *vsk = vsock_sk(sk);
+ struct sk_buff *skb;
+ size_t copied;
+ int err;
+
+ if (flags & (MSG_OOB | MSG_ERRQUEUE))
+ return -EOPNOTSUPP;
+
+ skb = skb_recv_datagram(sk, flags, &err);
+ if (!skb)
+ return err;
+
+ copied = min_t(size_t, len, skb->len);
+ if (copied < skb->len)
+ msg->msg_flags |= MSG_TRUNC;
+
+ err = skb_copy_datagram_iter(skb, 0, &msg->msg_iter, copied);
+ if (err)
+ goto out;
- return vsk->transport->dgram_dequeue(vsk, msg, len, flags);
+ if (msg->msg_name) {
+ memcpy(msg->msg_name, &VSOCK_DGRAM_SKB_CB(skb)->src,
+ sizeof(struct sockaddr_vm));
+ msg->msg_namelen = sizeof(struct sockaddr_vm);
+ }
+
+ err = flags & MSG_TRUNC ? skb->len : copied;
+out:
+ skb_free_datagram(sk, skb);
+ return err;
}
int vsock_dgram_recvmsg(struct socket *sock, struct msghdr *msg,
@@ -1641,10 +1852,7 @@ static int vsock_do_ioctl(struct socket *sock, unsigned int cmd,
spin_lock_bh(&sk->sk_receive_queue.lock);
skb = skb_peek(&sk->sk_receive_queue);
- if (skb && vsk->transport->dgram_payload_len)
- n_bytes = vsk->transport->dgram_payload_len(skb);
- else
- n_bytes = skb ? skb->len : 0;
+ n_bytes = skb ? skb->len : 0;
spin_unlock_bh(&sk->sk_receive_queue.lock);
} else {
n_bytes = vsock_stream_has_data(vsk);
@@ -3247,7 +3455,7 @@ int vsock_core_register(const struct vsock_transport *t, int features)
t_h2g = transport_h2g;
t_g2h = transport_g2h;
- t_dgram = transport_dgram;
+ t_dgram = transport_dgram_fallback;
t_local = transport_local;
if (features & VSOCK_TRANSPORT_F_H2G) {
@@ -3266,7 +3474,7 @@ int vsock_core_register(const struct vsock_transport *t, int features)
t_g2h = t;
}
- if (features & VSOCK_TRANSPORT_F_DGRAM) {
+ if (features & VSOCK_TRANSPORT_F_DGRAM_FALLBACK) {
if (t_dgram) {
err = -EBUSY;
goto err_busy;
@@ -3284,7 +3492,7 @@ int vsock_core_register(const struct vsock_transport *t, int features)
transport_h2g = t_h2g;
transport_g2h = t_g2h;
- transport_dgram = t_dgram;
+ transport_dgram_fallback = t_dgram;
transport_local = t_local;
err_busy:
@@ -3303,8 +3511,8 @@ void vsock_core_unregister(const struct vsock_transport *t)
if (transport_g2h == t)
transport_g2h = NULL;
- if (transport_dgram == t)
- transport_dgram = NULL;
+ if (transport_dgram_fallback == t)
+ transport_dgram_fallback = NULL;
if (transport_local == t)
transport_local = NULL;
diff --git a/net/vmw_vsock/hyperv_transport.c b/net/vmw_vsock/hyperv_transport.c
index 855b15971c35..015c5371ea98 100644
--- a/net/vmw_vsock/hyperv_transport.c
+++ b/net/vmw_vsock/hyperv_transport.c
@@ -443,6 +443,9 @@ static int hvs_sock_init(struct vsock_sock *vsk, struct vsock_sock *psk)
struct hvsock *hvs;
struct sock *sk = sk_vsock(vsk);
+ if (sk->sk_type == SOCK_DGRAM)
+ return -ESOCKTNOSUPPORT;
+
hvs = kzalloc_obj(*hvs);
if (!hvs)
return -ENOMEM;
@@ -556,13 +559,8 @@ static int hvs_dgram_bind(struct vsock_sock *vsk, struct sockaddr_vm *addr)
return -EOPNOTSUPP;
}
-static int hvs_dgram_dequeue(struct vsock_sock *vsk, struct msghdr *msg,
- size_t len, int flags)
-{
- return -EOPNOTSUPP;
-}
-
static int hvs_dgram_enqueue(struct vsock_sock *vsk,
+ const struct vsock_transport *transport,
struct sockaddr_vm *remote, struct msghdr *msg,
size_t dgram_len)
{
@@ -871,7 +869,6 @@ static struct vsock_transport hvs_transport = {
.shutdown = hvs_shutdown,
.dgram_bind = hvs_dgram_bind,
- .dgram_dequeue = hvs_dgram_dequeue,
.dgram_enqueue = hvs_dgram_enqueue,
.dgram_allow = hvs_dgram_allow,
diff --git a/net/vmw_vsock/virtio_transport.c b/net/vmw_vsock/virtio_transport.c
index fd10f20d7611..87a96e94c3c2 100644
--- a/net/vmw_vsock/virtio_transport.c
+++ b/net/vmw_vsock/virtio_transport.c
@@ -582,9 +582,9 @@ static struct virtio_transport virtio_transport = {
.cancel_pkt = virtio_transport_cancel_pkt,
.dgram_bind = virtio_transport_dgram_bind,
- .dgram_dequeue = virtio_transport_dgram_dequeue,
.dgram_enqueue = virtio_transport_dgram_enqueue,
.dgram_allow = virtio_transport_dgram_allow_check,
+ .dgram_transport_match = virtio_transport_dgram_transport_match,
.stream_dequeue = virtio_transport_stream_dequeue,
.stream_enqueue = virtio_transport_stream_enqueue,
diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
index 08baa1181ba6..3ef86f5eaf36 100644
--- a/net/vmw_vsock/virtio_transport_common.c
+++ b/net/vmw_vsock/virtio_transport_common.c
@@ -316,21 +316,30 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,
info->type = virtio_transport_get_type(sk_vsock(vsk));
- t_ops = virtio_transport_get_ops(vsk);
+ t_ops = info->t_ops ?: virtio_transport_get_ops(vsk);
if (unlikely(!t_ops))
return -EFAULT;
src_cid = t_ops->transport.get_local_cid();
src_port = vsk->local_addr.svm_port;
- if (!info->remote_cid) {
+ if (info->type != VIRTIO_VSOCK_TYPE_DGRAM && !info->remote_cid) {
dst_cid = vsk->remote_addr.svm_cid;
dst_port = vsk->remote_addr.svm_port;
} else {
dst_cid = info->remote_cid;
dst_port = info->remote_port;
}
+ /* Explicit loopback bindings keep their CID; wildcard bindings use
+ * the addressed local CID so connected peers can compare exact CIDs.
+ */
+ if (info->type == VIRTIO_VSOCK_TYPE_DGRAM &&
+ src_cid == VMADDR_CID_LOCAL)
+ src_cid = vsk->local_addr.svm_cid == VMADDR_CID_ANY ?
+ dst_cid : vsk->local_addr.svm_cid;
- /* Datagram sends use socket memory accounting instead of peer credits. */
+ /* Datagram sends use socket memory accounting, including when VMCI
+ * owns the socket and vsk->trans contains VMCI state.
+ */
vvs = info->type == VIRTIO_VSOCK_TYPE_DGRAM ? NULL : vsk->trans;
if (info->type == VIRTIO_VSOCK_TYPE_DGRAM) {
@@ -1013,55 +1022,6 @@ virtio_transport_seqpacket_enqueue(struct vsock_sock *vsk,
}
EXPORT_SYMBOL_GPL(virtio_transport_seqpacket_enqueue);
-int
-virtio_transport_dgram_dequeue(struct vsock_sock *vsk,
- struct msghdr *msg,
- size_t len, int flags)
-{
- struct sock *sk = sk_vsock(vsk);
- struct virtio_vsock_hdr *hdr;
- struct sk_buff *skb;
- size_t payload_len;
- int err;
-
- if (flags & (MSG_OOB | MSG_ERRQUEUE))
- return -EOPNOTSUPP;
-
- skb = skb_recv_datagram(sk, flags, &err);
- if (!skb)
- return err;
-
- hdr = virtio_vsock_hdr(skb);
- payload_len = skb->len;
-
- if (payload_len > len) {
- payload_len = len;
- msg->msg_flags |= MSG_TRUNC;
- }
-
- err = skb_copy_datagram_iter(skb, 0, &msg->msg_iter, payload_len);
- if (err)
- goto out;
-
- if (msg->msg_name) {
- DECLARE_SOCKADDR(struct sockaddr_vm *, vm_addr, msg->msg_name);
-
- vsock_addr_init(vm_addr, le64_to_cpu(hdr->src_cid),
- le32_to_cpu(hdr->src_port));
- msg->msg_namelen = sizeof(*vm_addr);
- }
-
- if (flags & MSG_TRUNC)
- err = skb->len;
- else
- err = payload_len;
-
-out:
- skb_free_datagram(sk, skb);
- return err;
-}
-EXPORT_SYMBOL_GPL(virtio_transport_dgram_dequeue);
-
s64 virtio_transport_stream_has_data(struct vsock_sock *vsk)
{
struct virtio_vsock_sock *vvs = vsk->trans;
@@ -1271,6 +1231,13 @@ bool virtio_transport_dgram_allow(struct vsock_sock *vsk, u32 cid, u32 port)
}
EXPORT_SYMBOL_GPL(virtio_transport_dgram_allow);
+bool virtio_transport_dgram_transport_match(const struct vsock_transport *t)
+{
+ return t && t->dgram_transport_match ==
+ virtio_transport_dgram_transport_match;
+}
+EXPORT_SYMBOL_GPL(virtio_transport_dgram_transport_match);
+
int virtio_transport_connect(struct vsock_sock *vsk)
{
struct virtio_vsock_pkt_info info = {
@@ -1301,12 +1268,14 @@ EXPORT_SYMBOL_GPL(virtio_transport_shutdown);
int
virtio_transport_dgram_enqueue(struct vsock_sock *vsk,
+ const struct vsock_transport *transport,
struct sockaddr_vm *remote_addr,
struct msghdr *msg,
size_t dgram_len)
{
struct virtio_vsock_pkt_info info = {
.op = VIRTIO_VSOCK_OP_RW,
+ .t_ops = container_of(transport, struct virtio_transport, transport),
.msg = msg,
.pkt_len = dgram_len,
.vsk = vsk,
@@ -1844,6 +1813,33 @@ static bool virtio_transport_valid_type(u16 type)
(type == VIRTIO_VSOCK_TYPE_DGRAM);
}
+static bool
+virtio_transport_dgram_source_allow(struct vsock_sock *vsk,
+ const struct vsock_transport *transport,
+ const struct sockaddr_vm *src)
+{
+ const struct vsock_transport *peer_transport;
+ struct sockaddr_vm peer = vsk->remote_addr;
+ bool allow;
+
+ if ((!virtio_transport_dgram_transport_match(vsk->transport) &&
+ !vsk->transport->dgram_fallback) ||
+ !virtio_transport_dgram_transport_match(transport))
+ return false;
+ /* Shutdown preserves the peer; only disconnect removes it. */
+ if (sk_vsock(vsk)->sk_socket->state == SS_UNCONNECTED)
+ return true;
+
+ peer_transport = vsock_dgram_get_transport(vsk, &peer);
+ if (IS_ERR(peer_transport))
+ return false;
+ allow = peer_transport == transport &&
+ src->svm_port == vsk->remote_addr.svm_port &&
+ src->svm_cid == vsk->remote_addr.svm_cid;
+ module_put(peer_transport->module);
+ return allow;
+}
+
/* We are under the virtio-vsock's vsock->rx_lock or vhost-vsock's vq->mutex
* lock.
*/
@@ -1891,14 +1887,21 @@ void virtio_transport_recv_pkt(struct virtio_transport *t,
vsk = vsock_sk(sk);
lock_sock(sk);
if (sock_flag(sk, SOCK_DEAD) ||
- vsk->transport != &t->transport ||
- (sk->sk_socket->state != SS_UNCONNECTED &&
- !vsock_check_source(vsk, &t->transport, &src))) {
+ !virtio_transport_dgram_source_allow(vsk, &t->transport, &src)) {
release_sock(sk);
sock_put(sk);
goto free_pkt;
}
+ /* Loopback can hand the TX skb to its receiver. Release TX
+ * ownership before replacing its private control block.
+ */
+ skb_orphan(skb);
+ vsock_addr_init(&VSOCK_DGRAM_SKB_CB(skb)->src,
+ src.svm_cid, src.svm_port);
+ if (t->transport.get_local_cid() > VMADDR_CID_HOST)
+ VSOCK_DGRAM_SKB_CB(skb)->src.svm_flags =
+ VMADDR_FLAG_TO_HOST;
if (sock_queue_rcv_skb(sk, skb))
kfree_skb(skb);
diff --git a/net/vmw_vsock/vmci_transport.c b/net/vmw_vsock/vmci_transport.c
index 2b9493e987d1..3c02c9a5319b 100644
--- a/net/vmw_vsock/vmci_transport.c
+++ b/net/vmw_vsock/vmci_transport.c
@@ -629,18 +629,21 @@ static int vmci_transport_recv_dgram_cb(void *data, struct vmci_datagram *dg)
vsk = vsock_sk(sk);
if (!vmci_transport_allow_dgram(vsk, dg->src.context))
return VMCI_ERROR_NO_ACCESS;
+ if (dg->payload_size > VMCI_MAX_DG_PAYLOAD_SIZE)
+ return VMCI_ERROR_INVALID_ARGS;
- size = VMCI_DG_SIZE(dg);
+ size = dg->payload_size;
- /* Attach the packet to the socket's receive queue as an sk_buff. */
+ /* Queue only the payload, with transport-independent source metadata. */
skb = alloc_skb(size, GFP_ATOMIC);
if (!skb)
return VMCI_ERROR_NO_MEM;
/* sk_receive_skb() will do a sock_put(), so hold here. */
sock_hold(sk);
- skb_put(skb, size);
- memcpy(skb->data, dg, size);
+ skb_put_data(skb, VMCI_DG_PAYLOAD(dg), size);
+ vsock_addr_init(&VSOCK_DGRAM_SKB_CB(skb)->src,
+ dg->src.context, dg->src.resource);
sk_receive_skb(sk, skb, 0);
return VMCI_SUCCESS;
@@ -1690,39 +1693,49 @@ static void vmci_transport_release(struct vsock_sock *vsk)
}
}
+#define VMCI_DGRAM_BIND_RETRIES 24
+
static int vmci_transport_dgram_bind(struct vsock_sock *vsk,
struct sockaddr_vm *addr)
{
+ const bool auto_port = addr->svm_port == VMADDR_PORT_ANY;
u32 port;
u32 flags;
+ unsigned int i;
int err;
- /* VMCI will select a resource ID for us if we provide
- * VMCI_INVALID_ID.
- */
- port = addr->svm_port == VMADDR_PORT_ANY ?
- VMCI_INVALID_ID : addr->svm_port;
-
- if (port <= LAST_RESERVED_PORT && !capable(CAP_NET_BIND_SERVICE))
- return -EACCES;
-
flags = addr->svm_cid == VMADDR_CID_ANY ?
VMCI_FLAG_ANYCID_DG_HND : 0;
- err = vmci_transport_datagram_create_hnd(port, flags,
- vmci_transport_recv_dgram_cb,
- &vsk->sk,
- &vmci_trans(vsk)->dg_handle);
- if (err < VMCI_SUCCESS)
- return vmci_transport_error_to_vsock_error(err);
- vsock_addr_init(&vsk->local_addr, addr->svm_cid,
- vmci_trans(vsk)->dg_handle.resource);
+ /* Reserve the same port for VMCI and directional receive. A VMCI
+ * resource may already occupy a core-selected ephemeral port.
+ */
+ for (i = 0; i < VMCI_DGRAM_BIND_RETRIES; i++) {
+ err = vsock_bind_common(vsk, addr);
+ if (err)
+ return err;
- return 0;
+ port = vsk->local_addr.svm_port;
+ err = vmci_transport_datagram_create_hnd(port, flags,
+ vmci_transport_recv_dgram_cb,
+ &vsk->sk,
+ &vmci_trans(vsk)->dg_handle);
+ if (err >= VMCI_SUCCESS)
+ return 0;
+
+ vsock_unbind_common(vsk);
+ vmci_trans(vsk)->dg_handle = VMCI_INVALID_HANDLE;
+ if (!auto_port || (err != VMCI_ERROR_ALREADY_EXISTS &&
+ err != VMCI_ERROR_DUPLICATE_ENTRY))
+ return vmci_transport_error_to_vsock_error(err);
+ }
+
+ return -EADDRNOTAVAIL;
}
static int vmci_transport_dgram_enqueue(
struct vsock_sock *vsk,
+ const struct vsock_transport *transport,
struct sockaddr_vm *remote_addr,
struct msghdr *msg,
size_t len)
@@ -1761,67 +1774,6 @@ static int vmci_transport_dgram_enqueue(
return err - sizeof(*dg);
}
-static size_t vmci_transport_dgram_payload_len(const struct sk_buff *skb)
-{
- if (skb->len < sizeof(struct vmci_datagram))
- return 0;
-
- return skb->len - sizeof(struct vmci_datagram);
-}
-
-static int vmci_transport_dgram_dequeue(struct vsock_sock *vsk,
- struct msghdr *msg, size_t len,
- int flags)
-{
- int err;
- struct vmci_datagram *dg;
- size_t payload_len;
- struct sk_buff *skb;
-
- if (flags & MSG_OOB || flags & MSG_ERRQUEUE)
- return -EOPNOTSUPP;
-
- /* Retrieve the head sk_buff from the socket's receive queue. */
- err = 0;
- skb = skb_recv_datagram(&vsk->sk, flags, &err);
- if (!skb)
- return err;
-
- dg = (struct vmci_datagram *)skb->data;
- if (!dg)
- /* err is 0, meaning we read zero bytes. */
- goto out;
-
- payload_len = dg->payload_size;
- /* Ensure the sk_buff matches the payload size claimed in the packet. */
- if (payload_len != skb->len - sizeof(*dg)) {
- err = -EINVAL;
- goto out;
- }
-
- if (payload_len > len) {
- payload_len = len;
- msg->msg_flags |= MSG_TRUNC;
- }
-
- /* Place the datagram payload in the user's iovec. */
- err = skb_copy_datagram_msg(skb, sizeof(*dg), msg, payload_len);
- if (err)
- goto out;
-
- if (msg->msg_name) {
- /* Provide the address of the sender. */
- DECLARE_SOCKADDR(struct sockaddr_vm *, vm_addr, msg->msg_name);
- vsock_addr_init(vm_addr, dg->src.context, dg->src.resource);
- msg->msg_namelen = sizeof(*vm_addr);
- }
- err = payload_len;
-
-out:
- skb_free_datagram(&vsk->sk, skb);
- return err;
-}
-
static bool vmci_transport_dgram_allow(struct vsock_sock *vsk, u32 cid,
u32 port)
{
@@ -2082,10 +2034,9 @@ static struct vsock_transport vmci_transport = {
.release = vmci_transport_release,
.connect = vmci_transport_connect,
.dgram_bind = vmci_transport_dgram_bind,
- .dgram_dequeue = vmci_transport_dgram_dequeue,
- .dgram_payload_len = vmci_transport_dgram_payload_len,
.dgram_enqueue = vmci_transport_dgram_enqueue,
.dgram_allow = vmci_transport_dgram_allow,
+ .dgram_fallback = true,
.stream_dequeue = vmci_transport_stream_dequeue,
.stream_enqueue = vmci_transport_stream_enqueue,
.stream_has_data = vmci_transport_stream_has_data,
@@ -2153,7 +2104,8 @@ static int __init vmci_transport_init(void)
/* Register only with dgram feature, other features (H2G, G2H) will be
* registered when the first host or guest becomes active.
*/
- err = vsock_core_register(&vmci_transport, VSOCK_TRANSPORT_F_DGRAM);
+ err = vsock_core_register(&vmci_transport,
+ VSOCK_TRANSPORT_F_DGRAM_FALLBACK);
if (err < 0)
goto err_unsubscribe;
diff --git a/net/vmw_vsock/vsock_loopback.c b/net/vmw_vsock/vsock_loopback.c
index 8068d1b6e851..645b10843f86 100644
--- a/net/vmw_vsock/vsock_loopback.c
+++ b/net/vmw_vsock/vsock_loopback.c
@@ -74,9 +74,9 @@ static struct virtio_transport loopback_transport = {
.cancel_pkt = vsock_loopback_cancel_pkt,
.dgram_bind = virtio_transport_dgram_bind,
- .dgram_dequeue = virtio_transport_dgram_dequeue,
.dgram_enqueue = virtio_transport_dgram_enqueue,
.dgram_allow = virtio_transport_dgram_allow,
+ .dgram_transport_match = virtio_transport_dgram_transport_match,
.stream_dequeue = virtio_transport_stream_dequeue,
.stream_enqueue = virtio_transport_stream_enqueue,
--
2.56.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH RFC v2 7/8] vsock: fragment datagrams across receive buffers
2026-11-13 12:47 [PATCH RFC v2 0/8] vsock: add datagram support to virtio and vhost Sergio Lopez
` (5 preceding siblings ...)
2026-11-13 12:47 ` [PATCH RFC v2 6/8] vsock: allow datagrams to use directional transports Sergio Lopez
@ 2026-11-13 12:47 ` Sergio Lopez
2026-11-13 12:47 ` [PATCH RFC v2 8/8] vsock: add datagram tests to vsock_test Sergio Lopez
2026-11-13 13:05 ` [PATCH RFC v2 0/8] vsock: add datagram support to virtio and vhost Michael S. Tsirkin
8 siblings, 0 replies; 13+ messages in thread
From: Sergio Lopez @ 2026-11-13 12:47 UTC (permalink / raw)
To: Stefano Garzarella, Bobby Eshleman, Alexei Starovoitov,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Bryan Tan, Vishnu Dasa,
Broadcom internal kernel review list, Stefan Hajnoczi,
Michael S. Tsirkin, Jason Wang, Eugenio Pérez, Xuan Zhuo,
Haiyang Zhang, Wei Liu, Dexuan Cui, Shuah Khan, Randy Dunlap
Cc: virtualization, netdev, linux-kernel, kvm, bpf, linux-hyperv,
Jonathan Corbet, linux-doc, Sergio Lopez
The guest already uses page-sized RX allocations. A datagram can exceed
one RX buffer. Growing every posted buffer to hold a full datagram would
increase virtqueue memory use and make small queued datagrams retain large
backing allocations. With delayed reads, a bounded receive queue could
then fill after only a few messages.
Define START and EOM flags for datagram packets. Mark outgoing messages
with both flags, and let vhost split a message across guest RX buffers,
keeping START on the first fragment and EOM on the last. When a data
packet meets a descriptor with no payload space, vhost returns that
descriptor with zero used bytes. It drops datagrams but requeues stream
and seqpacket data for a later buffer.
Reassemble consecutive fragments in the guest before tap or socket
delivery. Bound a partial message to 64 fragments and 64 KiB. Discard
incomplete messages on malformed packets, a new START, a peer mismatch,
an intervening non-datagram packet, or virtqueue teardown. Require complete
framing for host and loopback ingress.
A QEMU reproducer now delivers a 9 KiB datagram as one message and all
16 messages of a delayed-read burst. Paired host/guest selftests cover
64 KiB messages and the burst through both a userspace backend and the
patched Linux vhost backend, with stream and seqpacket controls. A nested
vhost-vsock guest posting header-only RX buffers also passes stream and
seqpacket receive tests.
Signed-off-by: Sergio Lopez <slp@redhat.com>
---
drivers/vhost/vsock.c | 15 +++++
include/uapi/linux/virtio_vsock.h | 8 +++
net/vmw_vsock/virtio_transport.c | 115 ++++++++++++++++++++++++++++++--
net/vmw_vsock/virtio_transport_common.c | 11 ++-
4 files changed, 141 insertions(+), 8 deletions(-)
diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c
index f4fb2ac732f5..1ff4a9aff907 100644
--- a/drivers/vhost/vsock.c
+++ b/drivers/vhost/vsock.c
@@ -185,6 +185,19 @@ vhost_transport_do_send_pkt(struct vhost_vsock *vsock,
* buffer, we split it using multiple buffers.
*/
if (payload_len > iov_len - sizeof(*hdr)) {
+ if (iov_len == sizeof(*hdr)) {
+ /* Connected sockets must retry with the next buffer. */
+ if (le16_to_cpu(hdr->type) ==
+ VIRTIO_VSOCK_TYPE_DGRAM)
+ kfree_skb(skb);
+ else
+ virtio_vsock_skb_queue_head(
+ &vsock->send_pkt_queue, skb);
+ vhost_add_used(vq, head, 0);
+ added = true;
+ continue;
+ }
+
payload_len = iov_len - sizeof(*hdr);
/* As we are copying pieces of large packet's buffer to
@@ -244,6 +257,8 @@ vhost_transport_do_send_pkt(struct vhost_vsock *vsock,
*/
if (VIRTIO_VSOCK_SKB_CB(skb)->offset < skb->len) {
hdr->flags |= cpu_to_le32(flags_to_restore);
+ if (le16_to_cpu(hdr->type) == VIRTIO_VSOCK_TYPE_DGRAM)
+ hdr->flags &= ~cpu_to_le32(VIRTIO_VSOCK_DGRAM_START);
/* We are queueing the same skb to handle
* the remaining bytes, and we want to deliver it
diff --git a/include/uapi/linux/virtio_vsock.h b/include/uapi/linux/virtio_vsock.h
index 27b4b2b8bf13..e1937f756eeb 100644
--- a/include/uapi/linux/virtio_vsock.h
+++ b/include/uapi/linux/virtio_vsock.h
@@ -101,6 +101,14 @@ enum virtio_vsock_shutdown {
enum virtio_vsock_rw {
VIRTIO_VSOCK_SEQ_EOM = 1,
VIRTIO_VSOCK_SEQ_EOR = 2,
+ /*
+ * With VIRTIO_VSOCK_F_DGRAM, START marks the first RW packet of a
+ * datagram and EOM marks the last. Both are set for a datagram that
+ * fits in one packet. Fragments must be consecutive on the receive
+ * virtqueue. Each packet's len is its fragment length.
+ */
+ VIRTIO_VSOCK_DGRAM_EOM = VIRTIO_VSOCK_SEQ_EOM,
+ VIRTIO_VSOCK_DGRAM_START = 4,
};
#endif /* _UAPI_LINUX_VIRTIO_VSOCK_H */
diff --git a/net/vmw_vsock/virtio_transport.c b/net/vmw_vsock/virtio_transport.c
index 87a96e94c3c2..c8c58a7dcec4 100644
--- a/net/vmw_vsock/virtio_transport.c
+++ b/net/vmw_vsock/virtio_transport.c
@@ -54,6 +54,8 @@ struct virtio_vsock {
bool rx_run;
int rx_buf_nr;
int rx_buf_max_nr;
+ struct sk_buff_head dgram_rx_queue;
+ u32 dgram_rx_len;
u32 guest_cid;
bool seqpacket_allow;
@@ -312,7 +314,7 @@ virtio_transport_cancel_pkt(struct vsock_sock *vsk)
static void virtio_vsock_rx_fill(struct virtio_vsock *vsock)
{
int total_len = VIRTIO_VSOCK_DEFAULT_RX_BUF_SIZE;
- struct scatterlist pkt, *p;
+ struct scatterlist pkt;
struct virtqueue *vq;
struct sk_buff *skb;
int ret;
@@ -325,9 +327,9 @@ static void virtio_vsock_rx_fill(struct virtio_vsock *vsock)
break;
memset(skb->head, 0, VIRTIO_VSOCK_SKB_HEADROOM);
+ virtio_vsock_skb_put(skb, total_len - VIRTIO_VSOCK_SKB_HEADROOM);
sg_init_one(&pkt, virtio_vsock_hdr(skb), total_len);
- p = &pkt;
- ret = virtqueue_add_sgs(vq, &p, 0, 1, skb, GFP_KERNEL);
+ ret = virtqueue_add_inbuf(vq, &pkt, 1, skb, GFP_KERNEL);
if (ret < 0) {
kfree_skb(skb);
break;
@@ -661,6 +663,93 @@ virtio_transport_dgram_allow_check(struct vsock_sock *vsk, u32 cid, u32 port)
return dgram_allow;
}
+#define VIRTIO_VSOCK_DGRAM_MAX_FRAGS 64
+
+/* The RX worker holds rx_lock while assembling fragments from one virtqueue. */
+static void virtio_vsock_dgram_reset(struct virtio_vsock *vsock)
+{
+ skb_queue_purge(&vsock->dgram_rx_queue);
+ vsock->dgram_rx_len = 0;
+}
+
+static bool virtio_vsock_dgram_same_peer(struct sk_buff *first,
+ struct sk_buff *next)
+{
+ const struct virtio_vsock_hdr *a = virtio_vsock_hdr(first);
+ const struct virtio_vsock_hdr *b = virtio_vsock_hdr(next);
+
+ return a->src_cid == b->src_cid && a->dst_cid == b->dst_cid &&
+ a->src_port == b->src_port && a->dst_port == b->dst_port;
+}
+
+static struct sk_buff *
+virtio_vsock_dgram_reassemble(struct virtio_vsock *vsock, struct sk_buff *skb)
+{
+ struct sk_buff_head *queue = &vsock->dgram_rx_queue;
+ struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
+ struct sk_buff *assembled, *fragment;
+ u32 flags = le32_to_cpu(hdr->flags);
+ int offset = 0;
+
+ if (le16_to_cpu(hdr->op) != VIRTIO_VSOCK_OP_RW ||
+ flags & ~(VIRTIO_VSOCK_DGRAM_START | VIRTIO_VSOCK_DGRAM_EOM))
+ goto drop_partial;
+
+ /* START also resynchronizes after an abandoned datagram. */
+ if (flags & VIRTIO_VSOCK_DGRAM_START)
+ virtio_vsock_dgram_reset(vsock);
+ else if (skb_queue_empty(queue))
+ goto drop_skb;
+
+ fragment = skb_peek(queue);
+ if (fragment && !virtio_vsock_dgram_same_peer(fragment, skb))
+ goto drop_partial;
+ if (skb->len > VIRTIO_VSOCK_MAX_PKT_BUF_SIZE - vsock->dgram_rx_len ||
+ skb_queue_len(queue) >= VIRTIO_VSOCK_DGRAM_MAX_FRAGS ||
+ (!(flags & VIRTIO_VSOCK_DGRAM_EOM) && !skb->len))
+ goto drop_partial;
+
+ if (flags & VIRTIO_VSOCK_DGRAM_EOM && skb_queue_empty(queue))
+ return skb;
+
+ vsock->dgram_rx_len += skb->len;
+ skb_queue_tail(queue, skb);
+ if (!(flags & VIRTIO_VSOCK_DGRAM_EOM))
+ return NULL;
+
+ assembled = virtio_vsock_alloc_skb(VIRTIO_VSOCK_SKB_HEADROOM +
+ vsock->dgram_rx_len, GFP_KERNEL);
+ if (!assembled)
+ goto reset;
+ virtio_vsock_skb_put(assembled, vsock->dgram_rx_len);
+ fragment = skb_peek(queue);
+ memcpy(virtio_vsock_hdr(assembled), virtio_vsock_hdr(fragment),
+ sizeof(*hdr));
+ hdr = virtio_vsock_hdr(assembled);
+ hdr->len = cpu_to_le32(vsock->dgram_rx_len);
+ hdr->flags = cpu_to_le32(VIRTIO_VSOCK_DGRAM_START |
+ VIRTIO_VSOCK_DGRAM_EOM);
+ skb_queue_walk(queue, fragment) {
+ if (skb_store_bits(assembled, offset, fragment->data,
+ fragment->len)) {
+ kfree_skb(assembled);
+ goto reset;
+ }
+ offset += fragment->len;
+ }
+ virtio_vsock_dgram_reset(vsock);
+ return assembled;
+
+drop_partial:
+ virtio_vsock_dgram_reset(vsock);
+drop_skb:
+ kfree_skb(skb);
+ return NULL;
+reset:
+ virtio_vsock_dgram_reset(vsock);
+ return NULL;
+}
+
static void virtio_transport_rx_work(struct work_struct *work)
{
struct virtio_vsock *vsock =
@@ -697,7 +786,8 @@ static void virtio_transport_rx_work(struct work_struct *work)
/* Drop short/long packets */
if (unlikely(len < sizeof(*hdr) ||
- len > virtio_vsock_skb_len(skb))) {
+ len > sizeof(*hdr) + skb->len)) {
+ virtio_vsock_dgram_reset(vsock);
kfree_skb(skb);
continue;
}
@@ -705,6 +795,7 @@ static void virtio_transport_rx_work(struct work_struct *work)
hdr = virtio_vsock_hdr(skb);
payload_len = le32_to_cpu(hdr->len);
if (unlikely(payload_len > len - sizeof(*hdr))) {
+ virtio_vsock_dgram_reset(vsock);
kfree_skb(skb);
continue;
}
@@ -713,12 +804,19 @@ static void virtio_transport_rx_work(struct work_struct *work)
if (le16_to_cpu(hdr->type) == VIRTIO_VSOCK_TYPE_DGRAM &&
(!vsock->dgram_allow ||
le64_to_cpu(hdr->dst_cid) != vsock->guest_cid)) {
+ virtio_vsock_dgram_reset(vsock);
kfree_skb(skb);
continue;
}
- if (payload_len)
- virtio_vsock_skb_put(skb, payload_len);
+ pskb_trim_unique(skb, payload_len);
+ if (le16_to_cpu(hdr->type) == VIRTIO_VSOCK_TYPE_DGRAM) {
+ skb = virtio_vsock_dgram_reassemble(vsock, skb);
+ if (!skb)
+ continue;
+ } else if (!skb_queue_empty(&vsock->dgram_rx_queue)) {
+ virtio_vsock_dgram_reset(vsock);
+ }
virtio_transport_deliver_tap_pkt(skb);
@@ -730,7 +828,8 @@ static void virtio_transport_rx_work(struct work_struct *work)
} while (!virtqueue_enable_cb(vq));
out:
- if (vsock->rx_buf_nr < vsock->rx_buf_max_nr / 2)
+ if (!vsock->rx_buf_nr ||
+ vsock->rx_buf_nr < vsock->rx_buf_max_nr / 2)
virtio_vsock_rx_fill(vsock);
out_nofill:
mutex_unlock(&vsock->rx_lock);
@@ -824,6 +923,7 @@ static void virtio_vsock_vqs_del(struct virtio_vsock *vsock)
mutex_lock(&vsock->rx_lock);
while ((skb = virtqueue_detach_unused_buf(vsock->vqs[VSOCK_VQ_RX])))
kfree_skb(skb);
+ virtio_vsock_dgram_reset(vsock);
mutex_unlock(&vsock->rx_lock);
mutex_lock(&vsock->tx_lock);
@@ -865,6 +965,7 @@ static int virtio_vsock_probe(struct virtio_device *vdev)
mutex_init(&vsock->tx_lock);
mutex_init(&vsock->rx_lock);
+ skb_queue_head_init(&vsock->dgram_rx_queue);
mutex_init(&vsock->event_lock);
skb_queue_head_init(&vsock->send_pkt_queue);
INIT_WORK(&vsock->rx_work, virtio_transport_rx_work);
diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
index 3ef86f5eaf36..d52b5663ed01 100644
--- a/net/vmw_vsock/virtio_transport_common.c
+++ b/net/vmw_vsock/virtio_transport_common.c
@@ -275,6 +275,13 @@ static struct sk_buff *virtio_transport_alloc_skb(struct virtio_vsock_pkt_info *
hdr->flags |= cpu_to_le32(VIRTIO_VSOCK_SEQ_EOR);
}
}
+ if (info->type == VIRTIO_VSOCK_TYPE_DGRAM) {
+ struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
+
+ /* Vhost may split this message across guest RX buffers. */
+ hdr->flags |= cpu_to_le32(VIRTIO_VSOCK_DGRAM_START |
+ VIRTIO_VSOCK_DGRAM_EOM);
+ }
if (info->reply)
virtio_vsock_skb_set_reply(skb);
@@ -1876,7 +1883,9 @@ void virtio_transport_recv_pkt(struct virtio_transport *t,
* datagrams are silently dropped (no RST).
*/
if (le16_to_cpu(hdr->type) == VIRTIO_VSOCK_TYPE_DGRAM) {
- if (le16_to_cpu(hdr->op) != VIRTIO_VSOCK_OP_RW)
+ if (le16_to_cpu(hdr->op) != VIRTIO_VSOCK_OP_RW ||
+ le32_to_cpu(hdr->flags) !=
+ (VIRTIO_VSOCK_DGRAM_START | VIRTIO_VSOCK_DGRAM_EOM))
goto free_pkt;
sk = vsock_find_bound_dgram_socket_net(&dst, net);
--
2.56.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH RFC v2 8/8] vsock: add datagram tests to vsock_test
2026-11-13 12:47 [PATCH RFC v2 0/8] vsock: add datagram support to virtio and vhost Sergio Lopez
` (6 preceding siblings ...)
2026-11-13 12:47 ` [PATCH RFC v2 7/8] vsock: fragment datagrams across receive buffers Sergio Lopez
@ 2026-11-13 12:47 ` Sergio Lopez
2026-11-13 13:14 ` sashiko-bot
2026-11-13 13:05 ` [PATCH RFC v2 0/8] vsock: add datagram support to virtio and vhost Michael S. Tsirkin
8 siblings, 1 reply; 13+ messages in thread
From: Sergio Lopez @ 2026-11-13 12:47 UTC (permalink / raw)
To: Stefano Garzarella, Bobby Eshleman, Alexei Starovoitov,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Bryan Tan, Vishnu Dasa,
Broadcom internal kernel review list, Stefan Hajnoczi,
Michael S. Tsirkin, Jason Wang, Eugenio Pérez, Xuan Zhuo,
Haiyang Zhang, Wei Liu, Dexuan Cui, Shuah Khan, Randy Dunlap
Cc: virtualization, netdev, linux-kernel, kvm, bpf, linux-hyperv,
Jonathan Corbet, linux-doc, Sergio Lopez
Cover local semantics, host/guest transfers, routing and queue
accounting. Synchronize capability-based skips and document manual
checks.
Probe VMCI and virtio/vhost independently for capability skips. VMCI can
own a datagram socket while a directional transport serves its peer.
Local cases require loopback, but do not require a directional transport.
The VMCI queue-length case skips when loopback would take self-CID traffic,
so a passing result exercises VMCI receive accounting. Document this
coexistence in the README.
Test that connected loopback datagrams reject a different local CID at
the same port, including after write shutdown, and that wildcard senders
use the addressed CID and receive replies.
Add a delayed-read burst in both directions. Send 16 distinct 512-byte
datagrams before the receiver reads, with a bounded SO_RCVBUF. Check
message content and source address and verify no extra packet remains.
Test that datagrams and stream/SEQPACKET sockets can bind the same CID
and port in both orders, including wildcard CIDs, and that each receives
only its own traffic. Stream and SEQPACKET binds must still conflict.
Signed-off-by: Sergio Lopez <slp@redhat.com>
---
tools/testing/vsock/Makefile | 3 +-
tools/testing/vsock/README | 31 +-
tools/testing/vsock/util.c | 62 +-
tools/testing/vsock/util.h | 10 +
tools/testing/vsock/vsock_test.c | 175 +++++-
tools/testing/vsock/vsock_test_dgram.c | 1000 ++++++++++++++++++++++++++++++++
tools/testing/vsock/vsock_test_dgram.h | 29 +
7 files changed, 1278 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..0afd12036552 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,33 @@ 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 can coexist with virtio/vhost: it owns the datagram endpoint while
+negotiated directional routes can serve individual destinations.
+
+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 (51, requiring BPF privileges) and nested routing test (52, requiring both
+virtio and vhost transports) need explicit selection. The VMCI queue-length
+test (56) requires VMCI without vsock_loopback, so self-directed traffic uses
+VMCI rather than loopback.
+The connected loopback peer test (57) checks same-port senders on different
+local CIDs and wildcard source addresses.
+The delayed-read burst test (58) queues 16 datagrams under a bounded receive
+buffer in each direction before the receiver reads them.
+The port-space test (59) binds a datagram and a stream or seqpacket listener
+to the same CID and port in both orders, then checks delivery to both sockets.
+
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..fe2409d84108 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"
@@ -2912,6 +2913,116 @@ 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,
+ },
+ {
+ .name = "SOCK_DGRAM connected loopback filters same-port CIDs",
+ .run_client = test_dgram_connected_cid,
+ .run_server = test_dgram_connected_cid,
+ .check = vsock_dgram_check_local,
+ .local = true,
+ },
+ {
+ .name = "SOCK_DGRAM delayed-read burst with bounded receive memory",
+ .run_client = test_dgram_burst,
+ .run_server = test_dgram_burst,
+ .check = vsock_dgram_check_peer,
+ },
+ {
+ .name = "SOCK_DGRAM shares port number with stream and seqpacket",
+ .run_client = test_dgram_port_space,
+ .run_server = test_dgram_port_space,
+ .check = vsock_dgram_check_local,
+ .local = true,
+ },
{},
};
@@ -2957,6 +3068,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 +3103,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 +3119,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 +3150,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 +3165,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 +3209,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 +3217,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..78bdf015a352
--- /dev/null
+++ b/tools/testing/vsock/vsock_test_dgram.c
@@ -0,0 +1,1000 @@
+// 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;
+ bool vmci;
+ 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 may own the socket while a directional transport serves peers.
+ * Check the drivers independently; connect() below probes the peer route.
+ */
+ vmci = !access("/sys/module/vmw_vsock_vmci_transport", F_OK);
+ if (vmci)
+ caps |= DGRAM_VMCI;
+ if (!vmci ||
+ !access("/sys/bus/virtio/drivers/vmw_vsock_virtio_transport", F_OK) ||
+ !access("/sys/class/misc/vhost-vsock", F_OK))
+ 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) {
+ /* 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 probe = needs;
+ unsigned int local, both, either;
+
+ /* A VMCI self-send uses loopback when it is registered. */
+ if (needs & DGRAM_VMCI)
+ probe |= DGRAM_LOOPBACK;
+ local = dgram_capabilities(opts, probe);
+ both = local;
+ either = local;
+
+ /* run_tests() has already synchronized explicit --pick/--skip
+ * selection. Both peers can now safely exchange capabilities.
+ */
+ if (!opts->local) {
+ unsigned int peer;
+
+ control_writeulong(local);
+ peer = control_readulong();
+ both &= peer;
+ either |= peer;
+ }
+ 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_VMCI) && (either & DGRAM_LOOPBACK))
+ return "unload vsock_loopback to exercise VMCI datagram receive";
+ 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_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);
+}
+
+static void check_connected_local_cid(unsigned int peer_cid,
+ unsigned int stray_cid, bool shutdown_write)
+{
+ int rx = bound_socket(VMADDR_CID_ANY);
+ int allowed = bound_socket(peer_cid);
+ int stray = dgram_socket();
+ struct sockaddr_vm peer = sock_name(allowed, false);
+ struct sockaddr_vm dest = local_address(rx);
+ struct sockaddr_vm src;
+ char byte;
+
+ bind_fd(stray, stray_cid, peer.svm_port);
+ connect_fd(rx, &peer);
+ if (shutdown_write)
+ check(shutdown(rx, SHUT_WR), "shutdown SHUT_WR");
+ /* Both sources have the connected peer's port on distinct local CIDs. */
+ send_packet(stray, &dest, "B", 1);
+ send_packet(allowed, &dest, "A", 1);
+ src = recv_packet(rx, &byte, 1, 0, 1, false);
+ if (byte != 'A' || src.svm_cid != peer.svm_cid ||
+ src.svm_port != peer.svm_port)
+ fail("connected socket accepted a different local CID");
+ expect_empty(rx);
+ close(stray);
+ close(allowed);
+ close(rx);
+}
+
+static void check_wildcard_local_cid(unsigned int cid)
+{
+ int rx = bound_socket(VMADDR_CID_ANY);
+ int tx = bound_socket(VMADDR_CID_ANY);
+ struct sockaddr_vm peer = address(cid, sock_name(tx, false).svm_port);
+ struct sockaddr_vm dest = address(cid, sock_name(rx, false).svm_port);
+ struct sockaddr_vm src;
+ char byte;
+
+ connect_fd(rx, &peer);
+ send_packet(tx, &dest, "W", 1);
+ src = recv_packet(rx, &byte, 1, 0, 1, false);
+ if (byte != 'W' || src.svm_cid != cid ||
+ src.svm_port != peer.svm_port)
+ fail("wildcard loopback source did not use the addressed CID");
+ send_packet(rx, &src, "R", 1);
+ src = recv_packet(tx, &byte, 1, 0, 1, false);
+ if (byte != 'R' || src.svm_cid != cid ||
+ src.svm_port != dest.svm_port)
+ fail("reply to wildcard loopback source failed");
+ expect_empty(rx);
+ close(tx);
+ close(rx);
+}
+
+void test_dgram_connected_cid(const struct test_opts *opts)
+{
+ unsigned int cid = local_cid();
+
+ check_connected_local_cid(cid, VMADDR_CID_LOCAL, false);
+ check_connected_local_cid(VMADDR_CID_LOCAL, cid, false);
+ check_connected_local_cid(cid, VMADDR_CID_LOCAL, true);
+ check_wildcard_local_cid(cid);
+ check_wildcard_local_cid(VMADDR_CID_LOCAL);
+}
+
+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_burst(const struct test_opts *opts)
+{
+ struct sockaddr_vm peer, src;
+ char data[512], buf[512];
+ int rcvbuf = 131072;
+ int fd = peer_socket(opts, &peer);
+ unsigned int phase, i;
+
+ for (phase = 0; phase < 2; phase++) {
+ bool sender = (opts->mode == TEST_MODE_CLIENT) == (phase == 0);
+
+ if (sender) {
+ control_expectln("BURST_READY");
+ for (i = 0; i < 16; i++) {
+ fill_payload(data, sizeof(data), phase * 16 + i);
+ send_packet(fd, &peer, data, sizeof(data));
+ }
+ wait_sent(fd);
+ control_writeln("BURST_DONE");
+ } else {
+ check(setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf,
+ sizeof(rcvbuf)), "SO_RCVBUF");
+ control_writeln("BURST_READY");
+ control_expectln("BURST_DONE");
+ for (i = 0; i < 16; i++) {
+ fill_payload(data, sizeof(data), phase * 16 + i);
+ src = recv_packet(fd, buf, sizeof(buf), 0,
+ sizeof(buf), false);
+ check_source(opts, &peer, &src);
+ if (memcmp(data, buf, sizeof(data)))
+ fail("delayed datagram %u in phase %u corrupted",
+ i, phase);
+ }
+ expect_empty(fd);
+ }
+ }
+ close(fd);
+}
+
+void test_dgram_port_space(const struct test_opts *opts)
+{
+ const int types[] = { SOCK_STREAM, SOCK_SEQPACKET };
+ const unsigned int cids[] = { VMADDR_CID_LOCAL, VMADDR_CID_ANY };
+ size_t i, j;
+ int order;
+
+ for (i = 0; i < ARRAY_SIZE(types); i++) {
+ for (j = 0; j < ARRAY_SIZE(cids); j++) {
+ for (order = 0; order < 2; order++) {
+ struct sockaddr_vm bound, dest;
+ int listener, rx, tx, client, accepted, other;
+ char byte;
+
+ if (order) {
+ rx = bound_socket(cids[j]);
+ bound = sock_name(rx, false);
+ listener = vsock_bind(cids[j], bound.svm_port,
+ types[i]);
+ } else {
+ listener = vsock_bind(cids[j], VMADDR_PORT_ANY,
+ types[i]);
+ bound = sock_name(listener, false);
+ rx = dgram_socket();
+ bind_fd(rx, cids[j], bound.svm_port);
+ }
+
+ /* STREAM and SEQPACKET still share one port space. */
+ other = socket(AF_VSOCK,
+ (types[i] == SOCK_STREAM ? SOCK_SEQPACKET :
+ SOCK_STREAM) | SOCK_CLOEXEC, 0);
+ check(other, "socket for connectible port collision");
+ expect_error(bind(other, (struct sockaddr *)&bound,
+ sizeof(bound)), EADDRINUSE,
+ "stream/seqpacket duplicate bind");
+ close(other);
+
+ check(listen(listener, 1), "listen on shared port");
+ dest = address(VMADDR_CID_LOCAL, bound.svm_port);
+ tx = dgram_socket();
+ send_packet(tx, &dest, "D", 1);
+ recv_packet(rx, &byte, 1, 0, 1, false);
+ if (byte != 'D')
+ fail("datagram lost on shared port");
+
+ client = vsock_bind(VMADDR_CID_LOCAL,
+ VMADDR_PORT_ANY, types[i]);
+ if (vsock_connect_fd(client, VMADDR_CID_LOCAL,
+ bound.svm_port))
+ fail("connect on shared port: %s",
+ strerror(errno));
+ wait_readable(listener);
+ accepted = accept4(listener, NULL, NULL,
+ SOCK_NONBLOCK | SOCK_CLOEXEC);
+ check(accepted, "accept on shared port");
+ send_byte(client, 1, 0);
+ wait_readable(accepted);
+ recv_byte(accepted, 1, 0);
+ expect_empty(rx);
+ close(accepted);
+ close(client);
+ close(tx);
+ close(rx);
+ close(listener);
+ }
+ }
+ }
+}
+
+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..5a1cb44ea7f1
--- /dev/null
+++ b/tools/testing/vsock/vsock_test_dgram.h
@@ -0,0 +1,29 @@
+/* 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_connected_cid(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_burst(const struct test_opts *opts);
+void test_dgram_port_space(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
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH RFC v2 0/8] vsock: add datagram support to virtio and vhost
2026-11-13 12:47 [PATCH RFC v2 0/8] vsock: add datagram support to virtio and vhost Sergio Lopez
` (7 preceding siblings ...)
2026-11-13 12:47 ` [PATCH RFC v2 8/8] vsock: add datagram tests to vsock_test Sergio Lopez
@ 2026-11-13 13:05 ` Michael S. Tsirkin
2026-11-13 13:49 ` Sergio Lopez Pascual
8 siblings, 1 reply; 13+ messages in thread
From: Michael S. Tsirkin @ 2026-11-13 13:05 UTC (permalink / raw)
To: Sergio Lopez
Cc: Stefano Garzarella, Bobby Eshleman, Alexei Starovoitov,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Bryan Tan, Vishnu Dasa,
Broadcom internal kernel review list, Stefan Hajnoczi, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Haiyang Zhang, Wei Liu, Dexuan Cui,
Shuah Khan, Randy Dunlap, virtualization, netdev, linux-kernel,
kvm, bpf, linux-hyperv, Jonathan Corbet, linux-doc
On Fri, Nov 13, 2026 at 01:47:33PM +0100, Sergio Lopez wrote:
> This RFC adds SOCK_DGRAM support to virtio-vsock and vhost-vsock through
> the existing AF_VSOCK API. It is intended for guest and host services
> that exchange independent requests, responses, or events and need message
> boundaries and sender addresses without a stream connection or application
> framing.
>
> VIRTIO_VSOCK_F_DGRAM gates virtio traffic in both directions. Each virtio
> send carries zero to 64 KiB of payload in one VIRTIO_VSOCK_TYPE_DGRAM
> packet. On the RX virtqueue, the device may split a datagram across
> page-sized guest buffers.
> START and EOM delimit the message; the guest reassembles at most 64
> fragments and 64 KiB before delivering it to a socket.
where do these limits come from? how does peer know not to send more?
Thanks!
> Invalid or
> incomplete messages are discarded. Datagram delivery is best effort:
> there are no peer credits, acknowledgements, or retransmissions. Send
> admission uses the socket send-memory limit, and SIOCOUTQ reports charged
> send memory.
>
> Datagram sockets have a separate port space from stream and seqpacket
> sockets. The latter two continue to share their port space. Datagram
> routes are chosen per destination among guest-to-host, host-to-guest, and
> loopback transports, with VMCI as a fallback. VMCI still owns its socket
> handle when it is present; loopback works without a directional transport.
> Hyper-V continues to reject datagram sockets.
>
> Bobby Eshleman and Amery Hung explored this feature in their v6 series
> and v7 work-in-progress branch. This series is a fresh implementation
> against the current vsock code. It follows the separate datagram port
> space, per-destination routing, and VMCI fallback requirements discussed
> there. For guest receive buffers, it uses bounded fragmentation and
> reassembly rather than full-message buffers or the earlier drop/error
> queue approach:
>
> https://lore.kernel.org/r/20240710212555.1617795-1-amery.hung@bytedance.com
> https://github.com/ameryhung/linux/tree/vsock-dgram-v7
>
> A corresponding virtio specification update has been drafted separately.
> The wire format and feature negotiation still need specification review
> before this series can be considered for merging.
>
> The first patches expose the core port allocator and add datagram queue
> reporting, then implement the common data path, guest and host feature
> negotiation, routing, and RX fragmentation. The last patch extends
> vsock_test with local and peer tests for message boundaries, routing,
> connected peers, port sharing, queue accounting, and delayed reads.
>
> Testing used QEMU with the F_DGRAM property patch below, an x86-64
> KASAN/lockdep kernel, paired guest tests, and a nested Linux vhost-vsock
> backend. The tested paths include 64 KiB messages, delayed-read bursts,
> feature-disabled ingress, stream and seqpacket controls, and header-only
> guest RX buffers. A temporary userspace backend still fails stream test
> 21 on both the series base and tip; that case passes with Linux vhost.
> Real VMCI and Hyper-V hardware were not exercised.
>
> https://lists.gnu.org/archive/html/qemu-devel/2026-10/msg08074.html
>
> Code was written and reviewed both manually and assisted by Codex
> (GPT-6.1-Sol) and Claude (Opus 4.6).
>
> Signed-off-by: Sergio Lopez <slp@redhat.com>
> ---
> Changes in v2:
> - Keep guest RX buffers page-sized; add START/EOM framing and bounded
> datagram reassembly (thanks to Alexei Starovoitov and Bobby Eshleman).
> - Preserve stream and seqpacket data when an RX buffer has no payload
> room.
> - Separate datagram and connection-oriented port spaces, and share
> receive metadata and queueing (thanks to Alexei Starovoitov).
> - Check negotiated support and destination CID on ingress, and require
> an exact connected peer CID and port (thanks to Sashiko AI bot).
> - Select a capable route for each destination, retain VMCI as fallback,
> and support loopback-only sockets (thanks to Alexei Starovoitov).
> - Preserve the connected peer address across temporary parent fallback
> (thanks to Sashiko AI bot).
> - Use socket write-memory accounting for datagram SIOCOUTQ instead of
> separate transport counters (thanks to Alexei Starovoitov).
> - Extend selftests for fragmentation, delayed reads, port sharing,
> connected peers, and capability-based skips; fix the test build and
> README entries (thanks to Alexei Starovoitov, Bobby Eshleman,
> and Sashiko AI bot).
> - Link to v1: https://lore.kernel.org/r/20261028-vsock-dgram-support-v1-0-e457cf7cee78@sinrega.org
>
> ---
> Sergio Lopez (8):
> vsock: expose the core port allocator to transports
> vsock: report datagram receive and send queue state
> vsock/virtio: implement the datagram data path
> vsock/virtio: negotiate datagram support on the guest
> vhost/vsock: negotiate datagram support with guests
> vsock: allow datagrams to use directional transports
> vsock: fragment datagrams across receive buffers
> vsock: add datagram tests to vsock_test
>
> Documentation/admin-guide/sysctl/net.rst | 8 +-
> drivers/vhost/vsock.c | 51 +-
> include/linux/virtio_vsock.h | 9 +-
> include/net/af_vsock.h | 30 +-
> include/uapi/linux/virtio_vsock.h | 10 +
> net/vmw_vsock/af_vsock.c | 459 +++++++++++---
> net/vmw_vsock/hyperv_transport.c | 11 +-
> net/vmw_vsock/virtio_transport.c | 167 ++++-
> net/vmw_vsock/virtio_transport_common.c | 164 ++++-
> net/vmw_vsock/vmci_transport.c | 115 ++--
> net/vmw_vsock/vsock_bpf.c | 3 +-
> net/vmw_vsock/vsock_loopback.c | 2 +-
> tools/testing/vsock/Makefile | 3 +-
> tools/testing/vsock/README | 31 +-
> tools/testing/vsock/util.c | 62 +-
> tools/testing/vsock/util.h | 10 +
> tools/testing/vsock/vsock_test.c | 175 +++++-
> tools/testing/vsock/vsock_test_dgram.c | 1000 ++++++++++++++++++++++++++++++
> tools/testing/vsock/vsock_test_dgram.h | 29 +
> 19 files changed, 2066 insertions(+), 273 deletions(-)
> ---
> base-commit: 65ba8f34ec886ec72b2c07ddc8cf3d815c32869d
> change-id: 20261027-vsock-dgram-support-683a4e0aebe7
>
> Best regards,
> --
> Sergio Lopez <slp@redhat.com>
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH RFC v2 8/8] vsock: add datagram tests to vsock_test
2026-11-13 12:47 ` [PATCH RFC v2 8/8] vsock: add datagram tests to vsock_test Sergio Lopez
@ 2026-11-13 13:14 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-11-13 13:14 UTC (permalink / raw)
To: Sergio Lopez
Cc: virtualization, kvm, bpf, linux-hyperv, Michael S. Tsirkin,
Eugenio Perez
Thank you for your contribution! Sashiko AI review found 3 potential
issues to consider:
- [Medium] vsock_test: local tests calling local_cid() fail without
virtio or vhost
- [Medium] vsock_test: false-positive DGRAM_VIRTIO and DGRAM_PEER in
dgram_capabilities()
- [Medium] vsock_test: VMCI coexistence fails in test_dgram_bind() and
local CID bind
--
commit 004d4f33594e6af5b36eeee37f6e14f8e43323f5
Author: Sergio Lopez <slp@redhat.com>
vsock: add datagram tests to vsock_test
This commit adds AF_VSOCK datagram tests to vsock_test covering local
loopback behavior, host/guest transfers, routing, and queue accounting,
along with a --local execution mode and capability-based test skipping.
> diff --git a/tools/testing/vsock/vsock_test_dgram.c b/tools/testing/vsock/vsock_test_dgram.c
> new file mode 100644
> index 0000000000000..78bdf015a352f
> --- /dev/null
> +++ b/tools/testing/vsock/vsock_test_dgram.c
[ ... ]
> +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;
> +}
[Severity: Medium]
Will test_dgram_source_cid(), test_dgram_bound_lookup(), and
test_dgram_connected_cid() abort the test run instead of skipping when
vsock_loopback is loaded without a virtio or vhost transport?
Those three tests are guarded only by vsock_dgram_check_local(), which
succeeds whenever vsock_loopback is registered, but they unconditionally
call local_cid(). Without a guest-to-host or host-to-guest transport,
vsock_dev_do_ioctl() falls back to transport_local and returns a local cid
of 1, causing local_cid() to call fail().
[ ... ]
> +static unsigned int dgram_capabilities(const struct test_opts *opts,
> + unsigned int needs)
> +{
> + unsigned int caps = DGRAM_SOCKET;
> + struct sockaddr_vm peer;
> + bool vmci;
> + 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 may own the socket while a directional transport serves peers.
> + * Check the drivers independently; connect() below probes the peer route.
> + */
> + vmci = !access("/sys/module/vmw_vsock_vmci_transport", F_OK);
> + if (vmci)
> + caps |= DGRAM_VMCI;
> + if (!vmci ||
> + !access("/sys/bus/virtio/drivers/vmw_vsock_virtio_transport", F_OK) ||
> + !access("/sys/class/misc/vhost-vsock", F_OK))
> + 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);
[Severity: Medium]
Can dgram_capabilities() report false-positive virtio and peer datagram
support when either vmw_vsock_vmci_transport is absent or the directional
transport did not negotiate datagrams?
When vmw_vsock_vmci_transport is not loaded, !vmci short-circuits the sysfs
checks and sets the virtio capability even if only vsock_loopback is
loaded; if opts->peer_cid is 2 without a guest-to-host transport,
vsock_use_local_transport() routes connect() to loopback and also sets the
peer capability. Conversely, when vmw_vsock_vmci_transport is loaded
alongside virtio or vhost without datagram support negotiated,
vsock_dgram_get_transport() falls back to vmci_transport during connect(),
so connect() succeeds and peer tests run and fail instead of skipping.
[ ... ]
> +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");
[ ... ]
> +static void check_connected_local_cid(unsigned int peer_cid,
> + unsigned int stray_cid, bool shutdown_write)
> +{
> + int rx = bound_socket(VMADDR_CID_ANY);
> + int allowed = bound_socket(peer_cid);
> + int stray = dgram_socket();
> + struct sockaddr_vm peer = sock_name(allowed, false);
> + struct sockaddr_vm dest = local_address(rx);
> + struct sockaddr_vm src;
> + char byte;
> +
> + bind_fd(stray, stray_cid, peer.svm_port);
[Severity: Medium]
Do test_dgram_bind() and check_connected_local_cid() fail when
vmw_vsock_vmci_transport is loaded alongside vsock_loopback?
When vmci_transport owns the datagram socket, vsock_dgram_sendmsg()
resolves a wildcard destination cid via transport->get_local_cid()
(vmci_get_context_id()) instead of 1, so test_dgram_bind() failseither in
send_packet() or on the src.svm_cid check. In check_connected_local_cid(),
vmci_transport_dgram_bind() passes flags = 0 to dg_create_handle() for any
non-wildcard cid, ignoring addr->svm_cid and registering both allowed and
stray under (vmci_get_context_id(), peer.svm_port), so binding stray fails
with -EADDRINUSE.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113-vsock-dgram-support-v2-0-bacfc85b71ce@redhat.com?part=8
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH RFC v2 6/8] vsock: allow datagrams to use directional transports
2026-11-13 12:47 ` [PATCH RFC v2 6/8] vsock: allow datagrams to use directional transports Sergio Lopez
@ 2026-11-13 13:39 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-11-13 13:39 UTC (permalink / raw)
To: Sergio Lopez
Cc: virtualization, kvm, bpf, linux-hyperv, Michael S. Tsirkin,
Eugenio Perez
Thank you for your contribution! Sashiko AI review found 8 potential
issues to consider:
- [High] vsock: Hyper-V stream connections broken in
vsock_use_local_transport()
- [High] vsock: vsock_select_transport() routes VMADDR_CID_HOST
datagrams to VMCI
- [High] vsock: explicit CID bind failure in vmci_transport_dgram_bind()
- [High] vsock/vmci: local netns isolation bypass in
vmci_transport_dgram_bind()
- [Medium] vsock: broken VMADDR_CID_ANY handling in
vsock_dgram_connect() and sendmsg()
- [Medium] vsock: bind rollback TOCTOU race in
virtio_transport_recv_pkt()
- [Medium] vsock/virtio: virtio_transport_recv_pkt() sets TO_HOST on
host datagrams
- [Low] vsock/virtio: unused to_host field in struct virtio_vsock_skb_cb
--
commit 99283d4e6bf0b4f58509834e3f59987834c1907c
Author: Sergio Lopez <slp@redhat.com>
vsock: allow datagrams to use directional transports
This commit routes AF_VSOCK datagrams across directional transports,
loopback, and the VMCI fallback transport while unifying datagram
receive queuing in the core socket layer.
Link: https://lore.kernel.org/all/DMGNAV67LK3F.2FNLTD5NVHDEA@gmail.com/
> diff --git a/include/linux/virtio_vsock.h b/include/linux/virtio_vsock.h
> index f91704731057e..16b869085a0ab 100644
> --- a/include/linux/virtio_vsock.h
> +++ b/include/linux/virtio_vsock.h
> @@ -12,6 +12,7 @@
> struct virtio_vsock_skb_cb {
> bool reply;
> bool tap_delivered;
> + bool to_host;
> u32 offset;
> };
[Severity: Low]
This isn't a bug, but struct virtio_vsock_skb_cb::to_host is never read or
written anywhere in the patch (receive-side VMADDR_FLAG_TO_HOST is stored
in VSOCK_DGRAM_SKB_CB(skb)->src.svm_flags instead).
> diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
> index aef5389eeca2c..7e52c7e5583c2 100644
> --- a/net/vmw_vsock/af_vsock.c
> +++ b/net/vmw_vsock/af_vsock.c
[ ... ]
> @@ -568,6 +569,8 @@ EXPORT_SYMBOL_GPL(vsock_enqueue_accept);
>
> static bool vsock_use_local_transport(unsigned int remote_cid)
> {
> + u32 local_cid;
> +
> lockdep_assert_held(&vsock_register_mutex);
>
> if (!transport_local)
> @@ -576,11 +579,50 @@ static bool vsock_use_local_transport(unsigned int remote_cid)
> if (remote_cid == VMADDR_CID_LOCAL)
> return true;
>
> - if (transport_g2h) {
> - return remote_cid == transport_g2h->get_local_cid();
> - } else {
> - return remote_cid == VMADDR_CID_HOST;
> - }
> + local_cid = transport_g2h ? transport_g2h->get_local_cid() :
> + VMADDR_CID_ANY;
> + if (local_cid == VMADDR_CID_ANY)
> + local_cid = VMADDR_CID_HOST;
> +
> + return remote_cid == local_cid;
> +}
[Severity: High]
Does replacing VMADDR_CID_ANY with VMADDR_CID_HOST here break Hyper-V
SOCK_STREAM connections when vsock_loopback is loaded?
hvs_transport registers as transport_g2h and its get_local_cid() callback
(hvs_get_local_cid()) always returns VMADDR_CID_ANY because Hyper-V guests
do not use a numeric guest CID. With this change,
vsock_use_local_transport(VMADDR_CID_HOST) returns true whenever
transport_local is registered, so vsock_select_transport() routes
outgoing guest-to-host connect() calls to loopback_transport instead of
hvs_transport, and incoming host connections in hvs_open_connection()
(which sets vnew->remote_addr.svm_cid = VMADDR_CID_HOST) fail the
!hvs_check_transport(vnew) check and are dropped.
> +
> +/* Select a route without changing socket ownership. The caller holds
> + * vsock_register_mutex and pins the returned transport before releasing it.
> + */
> +static const struct vsock_transport *
> +vsock_select_transport(struct vsock_sock *vsk,
> + const struct sockaddr_vm *remote_addr,
> + const struct vsock_transport *dgram_owner,
> + bool *to_host)
> +{
> + struct sock *sk = sk_vsock(vsk);
> + u32 cid = remote_addr->svm_cid;
> +
> + lockdep_assert_held(&vsock_register_mutex);
> + *to_host = false;
> +
> + if (vsock_use_local_transport(cid))
> + return transport_local;
> + /* VMCI may use a different local CID from the virtio transports. */
> + if (dgram_owner && dgram_owner->dgram_fallback &&
> + cid == dgram_owner->get_local_cid())
> + return dgram_owner;
> + if (cid <= VMADDR_CID_HOST ||
> + (remote_addr->svm_flags & VMADDR_FLAG_TO_HOST))
> + return transport_g2h;
[Severity: High]
On a virtio guest with vmw_vsock_vmci_transport loaded, doesn't checking
cid == dgram_owner->get_local_cid() before cid <= VMADDR_CID_HOST misroute
VMADDR_CID_HOST datagrams to VMCI instead of virtio_transport?
drivers/misc/vmw_vmci/vmci_driver.c:vmci_get_context_id() {
if (vmci_guest_code_active())
return vmci_get_vm_context_id();
else if (vmci_host_code_active())
return VMCI_HOST_CONTEXT_ID;
return VMCI_INVALID_ID;
}
Because vmci_drv_init() initializes the host device unconditionally on
module load, vmci_host_code_active() is true on any guest without a VMware
PCI device, so vmci_transport_get_local_cid() returns VMCI_HOST_CONTEXT_ID
(2, which equals VMADDR_CID_HOST). When a guest SOCK_DGRAM socket sends
or connects to VMADDR_CID_HOST, cid == dgram_owner->get_local_cid()
matches vmci_transport before reaching cid <= VMADDR_CID_HOST, causing
sends to fail in vmci_datagram_send() with -EHOSTUNREACH and connected
sockets to reject incoming virtio datagrams from the host in
virtio_transport_dgram_source_allow().
[ ... ]
> @@ -629,33 +673,28 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)
> vsk->remote_addr.svm_cid > VMADDR_CID_HOST)
> vsk->remote_addr.svm_flags |= VMADDR_FLAG_TO_HOST;
>
> - remote_flags = vsk->remote_addr.svm_flags;
> -
> mutex_lock(&vsock_register_mutex);
>
> switch (sk->sk_type) {
> case SOCK_DGRAM:
> - new_transport = transport_dgram;
> + /* VMCI must own bind() to create its receive handle, even when
> + * directional transports serve individual destinations.
> + */
> + if (transport_dgram_fallback)
> + new_transport = transport_dgram_fallback;
> + else if (transport_g2h)
> + new_transport = transport_g2h;
> + else if (transport_h2g)
> + new_transport = transport_h2g;
> + else
> + new_transport = transport_local;
> break;
[Severity: High]
Should vsock_assign_transport() (or vmci_transport_dgram_bind()) avoid
allocating a global VMCI datagram handle when the socket's netns is in
VSOCK_NET_MODE_LOCAL?
Assigning transport_dgram_fallback to SOCK_DGRAM sockets in a
VSOCK_NET_MODE_LOCAL namespace causes vmci_transport_dgram_bind() to call
vmci_transport_datagram_create_hnd(), which reserves the port in the
system-wide vmci_resource_table and registers vmci_transport_recv_dgram_cb().
Because vmci_resource_table is global and vmci_transport_recv_dgram_cb()
does not check vsock_net_mode_global(vsk), sockets in local-mode
namespaces collide on ports with init_net and can receive global VMCI
datagrams across the namespace isolation boundary.
[ ... ]
> @@ -1498,7 +1671,8 @@ static int vsock_dgram_sendmsg(struct socket *sock, struct msghdr *msg,
> */
>
> if (remote_addr->svm_cid == VMADDR_CID_ANY)
> - remote_addr->svm_cid = transport->get_local_cid();
> + remote_addr->svm_cid = transport->dgram_transport_match ?
> + VMADDR_CID_LOCAL : transport->get_local_cid();
>
> if (!vsock_addr_bound(remote_addr)) {
> err = -EINVAL;
> @@ -1508,27 +1682,32 @@ static int vsock_dgram_sendmsg(struct socket *sock, struct msghdr *msg,
> remote_addr = &vsk->remote_addr;
>
> if (remote_addr->svm_cid == VMADDR_CID_ANY)
> - remote_addr->svm_cid = transport->get_local_cid();
> + remote_addr->svm_cid = transport->dgram_transport_match ?
> + VMADDR_CID_LOCAL : transport->get_local_cid();
>
> /* XXX Should connect() or this function ensure remote_addr is
> * bound?
> */
> - if (!vsock_addr_bound(&vsk->remote_addr)) {
> + if (!vsock_addr_bound(remote_addr)) {
> err = -EINVAL;
> goto out;
> }
> + /* A fallback must not pin subsequent sends to the host. */
> + connected_addr = *remote_addr;
> + remote_addr = &connected_addr;
> } else {
> err = -EINVAL;
> goto out;
> }
>
> - if (!transport->dgram_allow(vsk, remote_addr->svm_cid,
> - remote_addr->svm_port)) {
> - err = -EINVAL;
> + transport = vsock_dgram_get_transport(vsk, remote_addr);
> + if (IS_ERR(transport)) {
> + err = PTR_ERR(transport);
> goto out;
> }
>
> - err = transport->dgram_enqueue(vsk, remote_addr, msg, len);
> + err = transport->dgram_enqueue(vsk, transport, remote_addr, msg, len);
> + module_put(transport->module);
>
> out:
> release_sock(sk);
> @@ -1542,6 +1721,7 @@ static int vsock_dgram_connect(struct socket *sock,
> struct sock *sk;
> struct vsock_sock *vsk;
> struct sockaddr_vm *remote_addr;
> + const struct vsock_transport *transport;
>
> sk = sock->sk;
> vsk = vsock_sk(sk);
> @@ -1563,11 +1743,16 @@ static int vsock_dgram_connect(struct socket *sock,
> if (err)
> goto out;
>
> - if (!vsk->transport->dgram_allow(vsk, remote_addr->svm_cid,
> - remote_addr->svm_port)) {
> - err = -EINVAL;
> + if (remote_addr->svm_cid == VMADDR_CID_ANY &&
> + vsk->transport->dgram_transport_match)
> + remote_addr->svm_cid = VMADDR_CID_LOCAL;
> +
> + transport = vsock_dgram_get_transport(vsk, remote_addr);
> + if (IS_ERR(transport)) {
> + err = PTR_ERR(transport);
> goto out;
> }
> + module_put(transport->module);
>
> memcpy(&vsk->remote_addr, remote_addr, sizeof(vsk->remote_addr));
[Severity: Medium]
What happens in vsock_dgram_connect() and vsock_dgram_sendmsg() when
remote_addr->svm_cid is VMADDR_CID_ANY and vmci_transport owns the socket
(so dgram_transport_match is NULL)?
In vsock_dgram_connect(), remote_addr->svm_cid is left as VMADDR_CID_ANY
(0xffffffffU) and passed to vsock_dgram_get_transport(). Because
0xffffffffU > VMADDR_CID_HOST, vsock_select_transport() takes the
g2h_fallback path when enabled and sets VMADDR_FLAG_TO_HOST on
vsk->remote_addr, while leaving vsk->remote_addr.svm_cid as VMADDR_CID_ANY
so virtio_transport_dgram_source_allow() drops all incoming packets until
sendmsg() is called.
In vsock_dgram_sendmsg(), the SS_CONNECTED path then mutates
vsk->remote_addr.svm_cid in place before copying it into connected_addr.
Moreover, if VMCI has no active personality, transport->get_local_cid()
returns VMCI_INVALID_ID (VMADDR_CID_ANY), causing vsock_addr_bound() in
vsock_dgram_sendmsg() to fail with -EINVAL and vsock_select_transport() in
vsock_dgram_connect() to match cid == dgram_owner->get_local_cid() instead
of loopback.
> diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
> index 08baa1181ba6d..3ef86f5eaf362 100644
> --- a/net/vmw_vsock/virtio_transport_common.c
> +++ b/net/vmw_vsock/virtio_transport_common.c
[ ... ]
> @@ -1891,14 +1887,21 @@ void virtio_transport_recv_pkt(struct virtio_transport *t,
> vsk = vsock_sk(sk);
> lock_sock(sk);
> if (sock_flag(sk, SOCK_DEAD) ||
> - vsk->transport != &t->transport ||
> - (sk->sk_socket->state != SS_UNCONNECTED &&
> - !vsock_check_source(vsk, &t->transport, &src))) {
> + !virtio_transport_dgram_source_allow(vsk, &t->transport, &src)) {
> release_sock(sk);
> sock_put(sk);
> goto free_pkt;
> }
[Severity: Medium]
Can virtio_transport_recv_pkt() deliver a datagram to an unbound socket or
the wrong port if vmci_transport_dgram_bind() rolls back the core bind?
CPU0 CPU1
__vsock_bind() virtio_transport_recv_pkt()
lock_sock(sk)
vmci_transport_dgram_bind()
vsock_bind_common(vsk, addr)
sk = vsock_find_bound_dgram_socket_net(&dst, ...)
lock_sock(sk) (blocks)
vmci_transport_datagram_create_hnd()
(fails)
vsock_unbind_common(vsk)
release_sock(sk)
(acquires lock_sock)
virtio_transport_dgram_source_allow()
sock_queue_rcv_skb(sk, skb) <-- queued on unbound socket
vsock_bind_common() publishes vsk in vsock_bind_table and drops
vsock_table_lock before vmci_transport_datagram_create_hnd() runs. If
handle creation fails, vsock_unbind_common() removes vsk or retries on
another port, but virtio_transport_recv_pkt() does not re-check that
vsk->local_addr still matches dst after acquiring lock_sock(sk).
> +
> + /* Loopback can hand the TX skb to its receiver. Release TX
> + * ownership before replacing its private control block.
> + */
> + skb_orphan(skb);
> + vsock_addr_init(&VSOCK_DGRAM_SKB_CB(skb)->src,
> + src.svm_cid, src.svm_port);
> + if (t->transport.get_local_cid() > VMADDR_CID_HOST)
> + VSOCK_DGRAM_SKB_CB(skb)->src.svm_flags =
> + VMADDR_FLAG_TO_HOST;
[Severity: Medium]
Should this also check src.svm_cid > VMADDR_CID_HOST before setting
VMADDR_FLAG_TO_HOST, matching vsock_assign_transport()?
On a guest using virtio_transport, t->transport.get_local_cid() is the
guest CID (> VMADDR_CID_HOST), so checking only get_local_cid() sets
VMADDR_FLAG_TO_HOST on every received datagram even when the sender is the
host itself (src.svm_cid == VMADDR_CID_HOST). recvmsg() then reports
VMADDR_FLAG_TO_HOST in msg_name for direct host-to-guest traffic, whereas
getpeername() on a socket connected to VMADDR_CID_HOST reports
svm_flags == 0.
> diff --git a/net/vmw_vsock/vmci_transport.c b/net/vmw_vsock/vmci_transport.c
> index 2b9493e987d1e..3c02c9a5319b9 100644
> --- a/net/vmw_vsock/vmci_transport.c
> +++ b/net/vmw_vsock/vmci_transport.c
[ ... ]
> @@ -1690,39 +1693,49 @@ static void vmci_transport_release(struct vsock_sock *vsk)
> }
> }
>
> +#define VMCI_DGRAM_BIND_RETRIES 24
> +
> static int vmci_transport_dgram_bind(struct vsock_sock *vsk,
> struct sockaddr_vm *addr)
> {
> + const bool auto_port = addr->svm_port == VMADDR_PORT_ANY;
> u32 port;
> u32 flags;
> + unsigned int i;
> int err;
>
> - /* VMCI will select a resource ID for us if we provide
> - * VMCI_INVALID_ID.
> - */
> - port = addr->svm_port == VMADDR_PORT_ANY ?
> - VMCI_INVALID_ID : addr->svm_port;
> -
> - if (port <= LAST_RESERVED_PORT && !capable(CAP_NET_BIND_SERVICE))
> - return -EACCES;
> -
> flags = addr->svm_cid == VMADDR_CID_ANY ?
> VMCI_FLAG_ANYCID_DG_HND : 0;
>
> - err = vmci_transport_datagram_create_hnd(port, flags,
> - vmci_transport_recv_dgram_cb,
> - &vsk->sk,
> - &vmci_trans(vsk)->dg_handle);
> - if (err < VMCI_SUCCESS)
> - return vmci_transport_error_to_vsock_error(err);
> - vsock_addr_init(&vsk->local_addr, addr->svm_cid,
> - vmci_trans(vsk)->dg_handle.resource);
> + /* Reserve the same port for VMCI and directional receive. A VMCI
> + * resource may already occupy a core-selected ephemeral port.
> + */
> + for (i = 0; i < VMCI_DGRAM_BIND_RETRIES; i++) {
> + err = vsock_bind_common(vsk, addr);
> + if (err)
> + return err;
>
> - return 0;
> + port = vsk->local_addr.svm_port;
> + err = vmci_transport_datagram_create_hnd(port, flags,
> + vmci_transport_recv_dgram_cb,
> + &vsk->sk,
> + &vmci_trans(vsk)->dg_handle);
[Severity: High]
What happens here when a user binds a SOCK_DGRAM socket to an explicit
local CID (such as VMADDR_CID_LOCAL or a virtio guest CID) while
vmw_vsock_vmci_transport is loaded?
drivers/misc/vmw_vmci/vmci_datagram.c:dg_create_handle() {
...
if ((flags & VMCI_FLAG_ANYCID_DG_HND) != 0) {
context_id = VMCI_INVALID_ID;
} else {
context_id = vmci_get_context_id();
if (context_id == VMCI_INVALID_ID)
return VMCI_ERROR_NO_RESOURCES;
}
...
}
Because addr->svm_cid != VMADDR_CID_ANY, flags is 0. If VMCI has no
active guest or host personality (vmci_get_context_id() == VMCI_INVALID_ID),
dg_create_handle() returns VMCI_ERROR_NO_RESOURCES and every explicit-CID
bind() fails with -ENOBUFS. If VMCI is active, dg_create_handle() binds
the VMCI handle to vmci_get_context_id() rather than addr->svm_cid, so a
socket bound to VMADDR_CID_LOCAL (or a virtio guest CID) still registers a
VMCI receiver on VMCI's context ID and can receive external VMCI datagrams.
Conversely, vsock_find_cid() only checks transport_g2h, transport_h2g, and
transport_local, so if VMCI's local CID differs from transport_g2h,
__vsock_bind() rejects binding to VMCI's own local CID with
-EADDRNOTAVAIL.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261113-vsock-dgram-support-v2-0-bacfc85b71ce@redhat.com?part=6
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH RFC v2 0/8] vsock: add datagram support to virtio and vhost
2026-11-13 13:05 ` [PATCH RFC v2 0/8] vsock: add datagram support to virtio and vhost Michael S. Tsirkin
@ 2026-11-13 13:49 ` Sergio Lopez Pascual
0 siblings, 0 replies; 13+ messages in thread
From: Sergio Lopez Pascual @ 2026-11-13 13:49 UTC (permalink / raw)
To: Michael S. Tsirkin
Cc: Stefano Garzarella, Bobby Eshleman, Alexei Starovoitov,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Bryan Tan, Vishnu Dasa,
Broadcom internal kernel review list, Stefan Hajnoczi, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Haiyang Zhang, Wei Liu, Dexuan Cui,
Shuah Khan, Randy Dunlap, virtualization, netdev, linux-kernel,
kvm, bpf, linux-hyperv, Jonathan Corbet, linux-doc
"Michael S. Tsirkin" <mst@redhat.com> writes:
> On Fri, Nov 13, 2026 at 01:47:33PM +0100, Sergio Lopez wrote:
>> This RFC adds SOCK_DGRAM support to virtio-vsock and vhost-vsock through
>> the existing AF_VSOCK API. It is intended for guest and host services
>> that exchange independent requests, responses, or events and need message
>> boundaries and sender addresses without a stream connection or application
>> framing.
>>
>> VIRTIO_VSOCK_F_DGRAM gates virtio traffic in both directions. Each virtio
>> send carries zero to 64 KiB of payload in one VIRTIO_VSOCK_TYPE_DGRAM
>> packet. On the RX virtqueue, the device may split a datagram across
>> page-sized guest buffers.
>> START and EOM delimit the message; the guest reassembles at most 64
>> fragments and 64 KiB before delivering it to a socket.
>
> where do these limits come from? how does peer know not to send more?
>
The 64 KiB limit comes from VIRTIO_VSOCK_MAX_PKT_BUF_SIZE. The number of
fragments is a reasonable number to prevent the driver from putting too
much strain on the device.
Both limits should be part of the spec. This is the relevant part from
the draft I have put together so far:
+When VIRTIO_VSOCK_F_DGRAM is negotiated, each rx virtqueue buffer supplied
+by the driver MUST have room for the header and at least 1024 payload bytes.
+The driver MUST discard a received datagram packet whose \field{dst_cid}
+differs from the current \field{guest_cid} configuration value.
+The driver MUST reassemble consecutive fragments with matching address
+fields and MUST NOT deliver a partial datagram. It MUST discard an orphaned
+fragment, a fragment with an unsupported flag or operation, and any
+incomplete datagram interrupted by a new START or an unrelated packet. It
+MUST discard a datagram if its total length exceeds 65536 bytes or it has
+more than 64 fragments.
Thanks!
Sergio.
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-11-13 13:49 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-13 12:47 [PATCH RFC v2 0/8] vsock: add datagram support to virtio and vhost Sergio Lopez
2026-11-13 12:47 ` [PATCH RFC v2 1/8] vsock: expose the core port allocator to transports Sergio Lopez
2026-11-13 12:47 ` [PATCH RFC v2 2/8] vsock: report datagram receive and send queue state Sergio Lopez
2026-11-13 12:47 ` [PATCH RFC v2 3/8] vsock/virtio: implement the datagram data path Sergio Lopez
2026-11-13 12:47 ` [PATCH RFC v2 4/8] vsock/virtio: negotiate datagram support on the guest Sergio Lopez
2026-11-13 12:47 ` [PATCH RFC v2 5/8] vhost/vsock: negotiate datagram support with guests Sergio Lopez
2026-11-13 12:47 ` [PATCH RFC v2 6/8] vsock: allow datagrams to use directional transports Sergio Lopez
2026-11-13 13:39 ` sashiko-bot
2026-11-13 12:47 ` [PATCH RFC v2 7/8] vsock: fragment datagrams across receive buffers Sergio Lopez
2026-11-13 12:47 ` [PATCH RFC v2 8/8] vsock: add datagram tests to vsock_test Sergio Lopez
2026-11-13 13:14 ` sashiko-bot
2026-11-13 13:05 ` [PATCH RFC v2 0/8] vsock: add datagram support to virtio and vhost Michael S. Tsirkin
2026-11-13 13:49 ` Sergio Lopez Pascual
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox