* [PATCH RFC 0/8] vsock: add datagram support to virtio and vhost
@ 2026-10-28 16:04 Sergio Lopez
2026-10-28 16:04 ` [PATCH RFC 1/8] vsock: expose the core port allocator to transports Sergio Lopez
` (8 more replies)
0 siblings, 9 replies; 10+ messages in thread
From: Sergio Lopez @ 2026-10-28 16:04 UTC (permalink / raw)
To: Stefano Garzarella, Bobby Eshleman, 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
Cc: virtualization, netdev, linux-kernel, kvm, bpf, linux-hyperv,
Sergio Lopez
This series implements SOCK_DGRAM support for virtio-vsock and
vhost-vsock, allowing applications to exchange connectionless messages
through the existing AF_VSOCK datagram API.
Support is negotiated through VIRTIO_VSOCK_F_DGRAM. Each datagram uses
a single VIRTIO_VSOCK_TYPE_DGRAM packet, supporting payloads from zero
bytes through 64 KiB. Pending sends are bounded by the socket send
buffer rather than peer credits, with queue accounting and poll
readiness reflecting pending output.
Datagrams use destination-based routing across guest-to-host,
host-to-guest and loopback transports while retaining the socket's
transport state. VMCI keeps priority as the dedicated datagram
transport. Guest RX buffers accommodate complete messages when
datagrams are negotiated, preserving message boundaries.
The patches first expose the core port allocator and add datagram
queue reporting, then implement the common data path, guest and host
feature negotiation, directional routing and RX buffer handling. The
final patch extends vsock_test with local and host/guest datagram
coverage, including peer filtering, route changes, message boundaries
and queue ioctls. Unsupported cases skip automatically.
Tested using QEMU with a patch that exposed the VIRTIO_VSOCK_F_DGRAM
feature:
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>
---
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: preserve host-to-guest datagram boundaries
vsock: add datagram tests to vsock_test
drivers/vhost/vsock.c | 37 +-
include/linux/virtio_vsock.h | 6 +
include/net/af_vsock.h | 14 +-
include/uapi/linux/virtio_vsock.h | 2 +
net/vmw_vsock/af_vsock.c | 206 ++++++--
net/vmw_vsock/hyperv_transport.c | 4 +
net/vmw_vsock/virtio_transport.c | 71 ++-
net/vmw_vsock/virtio_transport_common.c | 197 +++++++-
net/vmw_vsock/vmci_transport.c | 10 +
net/vmw_vsock/vsock_bpf.c | 3 +-
net/vmw_vsock/vsock_loopback.c | 1 +
tools/testing/vsock/Makefile | 3 +-
tools/testing/vsock/README | 23 +-
tools/testing/vsock/util.c | 62 +--
tools/testing/vsock/util.h | 10 +
tools/testing/vsock/vsock_test.c | 154 +++++-
tools/testing/vsock/vsock_test_dgram.c | 809 ++++++++++++++++++++++++++++++++
tools/testing/vsock/vsock_test_dgram.h | 26 +
18 files changed, 1545 insertions(+), 93 deletions(-)
---
base-commit: 65ba8f34ec886ec72b2c07ddc8cf3d815c32869d
change-id: 20261027-vsock-dgram-support-683a4e0aebe7
Best regards,
--
Sergio Lopez <slp@sinrega.org>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH RFC 1/8] vsock: expose the core port allocator to transports
2026-10-28 16:04 [PATCH RFC 0/8] vsock: add datagram support to virtio and vhost Sergio Lopez
@ 2026-10-28 16:04 ` Sergio Lopez
2026-10-28 16:04 ` [PATCH RFC 2/8] vsock: report datagram receive and send queue state Sergio Lopez
` (7 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Sergio Lopez @ 2026-10-28 16:04 UTC (permalink / raw)
To: Stefano Garzarella, Bobby Eshleman, 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
Cc: virtualization, netdev, linux-kernel, kvm, bpf, linux-hyperv,
Sergio Lopez
The core port allocator is currently private to connection-oriented
sockets. Virtio datagrams need the same bound-socket lookup tables 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.
Signed-off-by: Sergio Lopez <slp@redhat.com>
---
include/net/af_vsock.h | 1 +
net/vmw_vsock/af_vsock.c | 31 ++++++++++++++++++++++---------
2 files changed, 23 insertions(+), 9 deletions(-)
diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h
index e8481f12312e..19bd9c1c3e5b 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);
diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
index d0508887a2fb..5d1d85e5f519 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -832,8 +832,8 @@ 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 sockaddr_vm new_addr;
@@ -888,11 +888,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 +929,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 [flat|nested] 10+ messages in thread
* [PATCH RFC 2/8] vsock: report datagram receive and send queue state
2026-10-28 16:04 [PATCH RFC 0/8] vsock: add datagram support to virtio and vhost Sergio Lopez
2026-10-28 16:04 ` [PATCH RFC 1/8] vsock: expose the core port allocator to transports Sergio Lopez
@ 2026-10-28 16:04 ` Sergio Lopez
2026-10-28 16:04 ` [PATCH RFC 3/8] vsock/virtio: implement the datagram data path Sergio Lopez
` (6 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Sergio Lopez @ 2026-10-28 16:04 UTC (permalink / raw)
To: Stefano Garzarella, Bobby Eshleman, 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
Cc: virtualization, netdev, linux-kernel, kvm, bpf, linux-hyperv,
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 19bd9c1c3e5b..0c8b8a815b04 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 5d1d85e5f519..dc1add2d2e89 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -1344,16 +1344,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;
@@ -1617,10 +1621,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 [flat|nested] 10+ messages in thread
* [PATCH RFC 3/8] vsock/virtio: implement the datagram data path
2026-10-28 16:04 [PATCH RFC 0/8] vsock: add datagram support to virtio and vhost Sergio Lopez
2026-10-28 16:04 ` [PATCH RFC 1/8] vsock: expose the core port allocator to transports Sergio Lopez
2026-10-28 16:04 ` [PATCH RFC 2/8] vsock: report datagram receive and send queue state Sergio Lopez
@ 2026-10-28 16:04 ` Sergio Lopez
2026-10-28 16:04 ` [PATCH RFC 4/8] vsock/virtio: negotiate datagram support on the guest Sergio Lopez
` (5 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Sergio Lopez @ 2026-10-28 16:04 UTC (permalink / raw)
To: Stefano Garzarella, Bobby Eshleman, 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
Cc: virtualization, netdev, linux-kernel, kvm, bpf, linux-hyperv,
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. Charge
pending payload bytes before handing the skb to the backend and
release that charge exactly once on completion, rejection, or queued
drops.
Deliver incoming datagrams through the bound table without entering
the connection state machine. 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/linux/virtio_vsock.h | 2 +
include/uapi/linux/virtio_vsock.h | 2 +
net/vmw_vsock/virtio_transport_common.c | 150 +++++++++++++++++++++++++++++---
net/vmw_vsock/vsock_bpf.c | 3 +-
4 files changed, 142 insertions(+), 15 deletions(-)
diff --git a/include/linux/virtio_vsock.h b/include/linux/virtio_vsock.h
index f91704731057..0bccae661221 100644
--- a/include/linux/virtio_vsock.h
+++ b/include/linux/virtio_vsock.h
@@ -12,6 +12,8 @@
struct virtio_vsock_skb_cb {
bool reply;
bool tap_delivered;
+ /* Datagram payload is still charged to bytes_unsent. */
+ bool tx_accounted;
u32 offset;
};
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/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
index 1e762a480df4..ae33630d99e7 100644
--- a/net/vmw_vsock/virtio_transport_common.c
+++ b/net/vmw_vsock/virtio_transport_common.c
@@ -204,10 +204,18 @@ 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;
}
+static void virtio_transport_dgram_skb_destructor(struct sk_buff *skb)
+{
+ virtio_transport_consume_skb_sent(skb, false);
+ sock_wfree(skb);
+}
+
/* Returns new sk_buff on success, otherwise returns NULL. */
static struct sk_buff *virtio_transport_alloc_skb(struct virtio_vsock_pkt_info *info,
size_t payload_len,
@@ -303,6 +311,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;
@@ -329,12 +338,24 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,
vvs = 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 +369,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 +415,15 @@ 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) {
+ spin_lock_bh(&vvs->tx_lock);
+ vvs->bytes_unsent += skb_len;
+ spin_unlock_bh(&vvs->tx_lock);
+ VIRTIO_VSOCK_SKB_CB(skb)->tx_accounted = true;
+ skb->destructor = virtio_transport_dgram_skb_destructor;
+ } else {
+ virtio_transport_inc_tx_pkt(vvs, skb);
+ }
ret = t_ops->send_pkt(skb, info->net);
if (ret < 0)
@@ -416,7 +446,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 +651,9 @@ 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 ||
+ VIRTIO_VSOCK_SKB_CB(skb)->tx_accounted)) {
struct vsock_sock *vs = vsock_sk(s);
struct virtio_vsock_sock *vvs;
@@ -631,6 +664,9 @@ void virtio_transport_consume_skb_sent(struct sk_buff *skb, bool consume)
spin_unlock_bh(&vvs->tx_lock);
}
+ if (s && s->sk_type == SOCK_DGRAM)
+ VIRTIO_VSOCK_SKB_CB(skb)->tx_accounted = false;
+
if (consume)
consume_skb(skb);
}
@@ -999,7 +1035,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 +1278,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 +1322,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 +1857,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 +1892,41 @@ 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_socket_net(&dst, net);
+
+ if (!sk || sk->sk_type != SOCK_DGRAM) {
+ if (sk)
+ sock_put(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 [flat|nested] 10+ messages in thread
* [PATCH RFC 4/8] vsock/virtio: negotiate datagram support on the guest
2026-10-28 16:04 [PATCH RFC 0/8] vsock: add datagram support to virtio and vhost Sergio Lopez
` (2 preceding siblings ...)
2026-10-28 16:04 ` [PATCH RFC 3/8] vsock/virtio: implement the datagram data path Sergio Lopez
@ 2026-10-28 16:04 ` Sergio Lopez
2026-10-28 16:04 ` [PATCH RFC 5/8] vhost/vsock: negotiate datagram support with guests Sergio Lopez
` (4 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Sergio Lopez @ 2026-10-28 16:04 UTC (permalink / raw)
To: Stefano Garzarella, Bobby Eshleman, 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
Cc: virtualization, netdev, linux-kernel, kvm, bpf, linux-hyperv,
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.
Signed-off-by: Sergio Lopez <slp@redhat.com>
---
net/vmw_vsock/virtio_transport.c | 42 +++++++++++++++++++++++++++++++++++++---
1 file changed, 39 insertions(+), 3 deletions(-)
diff --git a/net/vmw_vsock/virtio_transport.c b/net/vmw_vsock/virtio_transport.c
index 4f9aa9c4c3aa..762e77895f28 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 =
@@ -835,6 +867,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 +967,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 [flat|nested] 10+ messages in thread
* [PATCH RFC 5/8] vhost/vsock: negotiate datagram support with guests
2026-10-28 16:04 [PATCH RFC 0/8] vsock: add datagram support to virtio and vhost Sergio Lopez
` (3 preceding siblings ...)
2026-10-28 16:04 ` [PATCH RFC 4/8] vsock/virtio: negotiate datagram support on the guest Sergio Lopez
@ 2026-10-28 16:04 ` Sergio Lopez
2026-10-28 16:04 ` [PATCH RFC 6/8] vsock: allow datagrams to use directional transports Sergio Lopez
` (3 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Sergio Lopez @ 2026-10-28 16:04 UTC (permalink / raw)
To: Stefano Garzarella, Bobby Eshleman, 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
Cc: virtualization, netdev, linux-kernel, kvm, bpf, linux-hyperv,
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.
Signed-off-by: Sergio Lopez <slp@redhat.com>
---
drivers/vhost/vsock.c | 28 ++++++++++++++++++++++++++--
1 file changed, 26 insertions(+), 2 deletions(-)
diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c
index 0cb87971d749..eb2fbdb721aa 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,
@@ -737,6 +759,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 +913,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 [flat|nested] 10+ messages in thread
* [PATCH RFC 6/8] vsock: allow datagrams to use directional transports
2026-10-28 16:04 [PATCH RFC 0/8] vsock: add datagram support to virtio and vhost Sergio Lopez
` (4 preceding siblings ...)
2026-10-28 16:04 ` [PATCH RFC 5/8] vhost/vsock: negotiate datagram support with guests Sergio Lopez
@ 2026-10-28 16:04 ` Sergio Lopez
2026-10-28 16:04 ` [PATCH RFC 7/8] vsock: preserve host-to-guest datagram boundaries Sergio Lopez
` (2 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Sergio Lopez @ 2026-10-28 16:04 UTC (permalink / raw)
To: Stefano Garzarella, Bobby Eshleman, 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
Cc: virtualization, netdev, linux-kernel, kvm, bpf, linux-hyperv,
Sergio Lopez
Use a dedicated datagram transport when present; otherwise initialize
socket state through a directional transport, falling back to vhost
when the guest transport lacks datagram support. Keep VMCI's existing
priority and reject datagrams during Hyper-V socket initialization.
Select a compatible transport for each datagram destination using the
stream routing policy. Keep the socket's transport state intact for
queued sends, pin the selected backend during dispatch, and check
connected sources against the peer's route. Preserve parent routing in
returned addresses and when connecting through the guest-to-host
fallback. Preserve explicit source CIDs on loopback datagrams so
replies reach the bound socket. Reject loopback source bindings whose
reply addresses select another transport.
Remove sockets from the core tables even when transport initialization
fails.
Signed-off-by: Sergio Lopez <slp@redhat.com>
---
drivers/vhost/vsock.c | 1 +
include/linux/virtio_vsock.h | 4 +
include/net/af_vsock.h | 11 ++-
net/vmw_vsock/af_vsock.c | 143 +++++++++++++++++++++++++++++---
net/vmw_vsock/hyperv_transport.c | 4 +
net/vmw_vsock/virtio_transport.c | 1 +
net/vmw_vsock/virtio_transport_common.c | 53 ++++++++++--
net/vmw_vsock/vmci_transport.c | 1 +
net/vmw_vsock/vsock_loopback.c | 1 +
9 files changed, 201 insertions(+), 18 deletions(-)
diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c
index eb2fbdb721aa..79ffdb28f4d9 100644
--- a/drivers/vhost/vsock.c
+++ b/drivers/vhost/vsock.c
@@ -465,6 +465,7 @@ static struct virtio_transport vhost_transport = {
.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 0bccae661221..91945031fcc5 100644
--- a/include/linux/virtio_vsock.h
+++ b/include/linux/virtio_vsock.h
@@ -14,6 +14,7 @@ struct virtio_vsock_skb_cb {
bool tap_delivered;
/* Datagram payload is still charged to bytes_unsent. */
bool tx_accounted;
+ bool to_host;
u32 offset;
};
@@ -173,6 +174,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;
@@ -263,6 +265,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);
@@ -276,6 +279,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 0c8b8a815b04..f9bcd703a776 100644
--- a/include/net/af_vsock.h
+++ b/include/net/af_vsock.h
@@ -121,9 +121,13 @@ struct vsock_transport {
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: another transport can use this socket's datagram state. */
+ bool (*dgram_transport_match)(const struct vsock_transport *transport);
/* Optional payload length override, called with the receive queue locked. */
size_t (*dgram_payload_len)(const struct sk_buff *skb);
@@ -240,6 +244,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 dc1add2d2e89..6419498a6fe2 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -136,6 +136,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>
@@ -599,6 +600,7 @@ 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;
struct sock *sk = sk_vsock(vsk);
unsigned int remote_cid = vsk->remote_addr.svm_cid;
__u8 remote_flags;
@@ -622,7 +624,12 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)
switch (sk->sk_type) {
case SOCK_DGRAM:
- new_transport = transport_dgram;
+ if (transport_dgram)
+ new_transport = transport_dgram;
+ else if (transport_g2h)
+ new_transport = transport_g2h;
+ else
+ new_transport = transport_h2g;
break;
case SOCK_STREAM:
case SOCK_SEQPACKET:
@@ -661,6 +668,11 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)
ret = -ENODEV;
goto err;
}
+ if (sk->sk_type == SOCK_DGRAM && !transport_dgram &&
+ new_transport == transport_g2h && transport_h2g &&
+ transport_h2g != new_transport &&
+ try_module_get(transport_h2g->module))
+ fallback_transport = transport_h2g;
/* 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
@@ -697,6 +709,14 @@ 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 (fallback_transport)
+ module_put(fallback_transport->module);
if (ret) {
module_put(new_transport->module);
return ret;
@@ -711,6 +731,98 @@ 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. Dedicated datagram transports retain their
+ * original selection; compatible transports share the socket's existing state.
+ * 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;
+ struct sock *sk = sk_vsock(vsk);
+ u32 cid = remote_addr->svm_cid;
+ u32 local_cid;
+ bool to_host = false;
+ int err = -ENODEV;
+
+ if (!owner)
+ return ERR_PTR(-ENODEV);
+
+ mutex_lock(&vsock_register_mutex);
+ if (!owner->dgram_transport_match) {
+ transport = owner;
+ } else if (vsock_use_local_transport(cid)) {
+ transport = transport_local;
+ } else if (cid <= VMADDR_CID_HOST ||
+ (remote_addr->svm_flags & VMADDR_FLAG_TO_HOST)) {
+ transport = transport_g2h;
+ } else if (transport_h2g &&
+ (!transport_h2g->has_remote_cid ||
+ transport_h2g->has_remote_cid(vsk, cid))) {
+ transport = transport_h2g;
+ } else if (sock_net(sk)->vsock.g2h_fallback && transport_g2h &&
+ transport_g2h->has_remote_cid &&
+ transport_g2h->has_remote_cid(vsk, cid)) {
+ transport = transport_g2h;
+ to_host = true;
+ } else {
+ transport = transport_h2g;
+ }
+
+ 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 (owner->dgram_transport_match && 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 (owner->dgram_transport_match) {
+ 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`.
@@ -1040,7 +1152,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);
@@ -1483,7 +1595,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;
@@ -1493,7 +1606,8 @@ 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?
@@ -1507,13 +1621,14 @@ static int vsock_dgram_sendmsg(struct socket *sock, struct msghdr *msg,
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);
@@ -1527,6 +1642,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);
@@ -1548,11 +1664,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;
diff --git a/net/vmw_vsock/hyperv_transport.c b/net/vmw_vsock/hyperv_transport.c
index 855b15971c35..6a276ef6964b 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;
@@ -563,6 +566,7 @@ static int hvs_dgram_dequeue(struct vsock_sock *vsk, struct msghdr *msg,
}
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)
{
diff --git a/net/vmw_vsock/virtio_transport.c b/net/vmw_vsock/virtio_transport.c
index 762e77895f28..14ce0836bc39 100644
--- a/net/vmw_vsock/virtio_transport.c
+++ b/net/vmw_vsock/virtio_transport.c
@@ -585,6 +585,7 @@ static struct virtio_transport virtio_transport = {
.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 ae33630d99e7..7c1ec8feb853 100644
--- a/net/vmw_vsock/virtio_transport_common.c
+++ b/net/vmw_vsock/virtio_transport_common.c
@@ -322,13 +322,18 @@ 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();
+ /* Preserve explicit datagram bindings so loopback replies can find them. */
+ if (info->type == VIRTIO_VSOCK_TYPE_DGRAM &&
+ src_cid == VMADDR_CID_LOCAL &&
+ vsk->local_addr.svm_cid != VMADDR_CID_ANY)
+ src_cid = vsk->local_addr.svm_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 {
@@ -1066,6 +1071,8 @@ virtio_transport_dgram_dequeue(struct vsock_sock *vsk,
vsock_addr_init(vm_addr, le64_to_cpu(hdr->src_cid),
le32_to_cpu(hdr->src_port));
msg->msg_namelen = sizeof(*vm_addr);
+ if (VIRTIO_VSOCK_SKB_CB(skb)->to_host)
+ vm_addr->svm_flags = VMADDR_FLAG_TO_HOST;
}
if (flags & MSG_TRUNC)
@@ -1288,6 +1295,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 = {
@@ -1318,12 +1332,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,
@@ -1861,6 +1877,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) ||
+ !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 ||
+ transport->get_local_cid() == VMADDR_CID_LOCAL);
+ module_put(peer_transport->module);
+ return allow;
+}
+
/* We are under the virtio-vsock's vsock->rx_lock or vhost-vsock's vq->mutex
* lock.
*/
@@ -1911,14 +1954,14 @@ 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;
}
+ VIRTIO_VSOCK_SKB_CB(skb)->to_host =
+ t->transport.get_local_cid() > VMADDR_CID_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..410deb2493c6 100644
--- a/net/vmw_vsock/vmci_transport.c
+++ b/net/vmw_vsock/vmci_transport.c
@@ -1723,6 +1723,7 @@ static int vmci_transport_dgram_bind(struct vsock_sock *vsk,
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)
diff --git a/net/vmw_vsock/vsock_loopback.c b/net/vmw_vsock/vsock_loopback.c
index 8068d1b6e851..d7bff1da2b74 100644
--- a/net/vmw_vsock/vsock_loopback.c
+++ b/net/vmw_vsock/vsock_loopback.c
@@ -77,6 +77,7 @@ static struct virtio_transport loopback_transport = {
.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 [flat|nested] 10+ messages in thread
* [PATCH RFC 7/8] vsock: preserve host-to-guest datagram boundaries
2026-10-28 16:04 [PATCH RFC 0/8] vsock: add datagram support to virtio and vhost Sergio Lopez
` (5 preceding siblings ...)
2026-10-28 16:04 ` [PATCH RFC 6/8] vsock: allow datagrams to use directional transports Sergio Lopez
@ 2026-10-28 16:04 ` Sergio Lopez
2026-10-28 16:04 ` [PATCH RFC 8/8] vsock: add datagram tests to vsock_test Sergio Lopez
2026-10-28 17:25 ` [PATCH RFC 0/8] vsock: add datagram support to virtio and vhost Alexei Starovoitov
8 siblings, 0 replies; 10+ messages in thread
From: Sergio Lopez @ 2026-10-28 16:04 UTC (permalink / raw)
To: Stefano Garzarella, Bobby Eshleman, 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
Cc: virtualization, netdev, linux-kernel, kvm, bpf, linux-hyperv,
Sergio Lopez
Vhost splits large datagrams across guest RX buffers, delivering each
fragment as a separate message. Allocate full-size RX buffers with
page fragments when datagrams are negotiated, and drop whole datagrams
if a guest buffer is too small. Refill empty RX queues even when only
one full-size buffer fits in the ring.
Signed-off-by: Sergio Lopez <slp@redhat.com>
---
drivers/vhost/vsock.c | 8 ++++++++
net/vmw_vsock/virtio_transport.c | 28 +++++++++++++++++++---------
2 files changed, 27 insertions(+), 9 deletions(-)
diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c
index 79ffdb28f4d9..2294ed8d30a8 100644
--- a/drivers/vhost/vsock.c
+++ b/drivers/vhost/vsock.c
@@ -185,6 +185,14 @@ vhost_transport_do_send_pkt(struct vhost_vsock *vsock,
* buffer, we split it using multiple buffers.
*/
if (payload_len > iov_len - sizeof(*hdr)) {
+ /* Datagrams must fit in a single descriptor chain. */
+ if (le16_to_cpu(hdr->type) == VIRTIO_VSOCK_TYPE_DGRAM) {
+ kfree_skb(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
diff --git a/net/vmw_vsock/virtio_transport.c b/net/vmw_vsock/virtio_transport.c
index 14ce0836bc39..d256274088b3 100644
--- a/net/vmw_vsock/virtio_transport.c
+++ b/net/vmw_vsock/virtio_transport.c
@@ -54,6 +54,7 @@ struct virtio_vsock {
bool rx_run;
int rx_buf_nr;
int rx_buf_max_nr;
+ struct scatterlist in_bufs[MAX_SKB_FRAGS + 1];
u32 guest_cid;
bool seqpacket_allow;
@@ -312,22 +313,31 @@ 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 virtqueue *vq;
struct sk_buff *skb;
int ret;
vq = vsock->vqs[VSOCK_VQ_RX];
+ /* A datagram must fit in one RX buffer, including its header. */
+ if (vsock->dgram_allow)
+ total_len = VIRTIO_VSOCK_SKB_HEADROOM +
+ VIRTIO_VSOCK_MAX_PKT_BUF_SIZE;
do {
- skb = virtio_vsock_alloc_linear_skb(total_len, GFP_KERNEL);
+ skb = virtio_vsock_alloc_skb(total_len, GFP_KERNEL);
if (!skb)
break;
memset(skb->head, 0, 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);
+ /* Post the header and the entire payload as one RX buffer. */
+ virtio_vsock_skb_put(skb, total_len - VIRTIO_VSOCK_SKB_HEADROOM);
+ skb_push(skb, VIRTIO_VSOCK_SKB_HEADROOM);
+ sg_init_table(vsock->in_bufs, ARRAY_SIZE(vsock->in_bufs));
+ ret = skb_to_sgvec(skb, vsock->in_bufs, 0, total_len);
+ skb_pull(skb, VIRTIO_VSOCK_SKB_HEADROOM);
+ if (ret >= 0)
+ ret = virtqueue_add_inbuf(vq, vsock->in_bufs, ret, skb,
+ GFP_KERNEL);
if (ret < 0) {
kfree_skb(skb);
break;
@@ -698,7 +708,7 @@ 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)) {
kfree_skb(skb);
continue;
}
@@ -710,8 +720,7 @@ static void virtio_transport_rx_work(struct work_struct *work)
continue;
}
- if (payload_len)
- virtio_vsock_skb_put(skb, payload_len);
+ pskb_trim_unique(skb, payload_len);
virtio_transport_deliver_tap_pkt(skb);
@@ -723,7 +732,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);
--
2.56.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH RFC 8/8] vsock: add datagram tests to vsock_test
2026-10-28 16:04 [PATCH RFC 0/8] vsock: add datagram support to virtio and vhost Sergio Lopez
` (6 preceding siblings ...)
2026-10-28 16:04 ` [PATCH RFC 7/8] vsock: preserve host-to-guest datagram boundaries Sergio Lopez
@ 2026-10-28 16:04 ` Sergio Lopez
2026-10-28 17:25 ` [PATCH RFC 0/8] vsock: add datagram support to virtio and vhost Alexei Starovoitov
8 siblings, 0 replies; 10+ messages in thread
From: Sergio Lopez @ 2026-10-28 16:04 UTC (permalink / raw)
To: Stefano Garzarella, Bobby Eshleman, 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
Cc: virtualization, netdev, linux-kernel, kvm, bpf, linux-hyperv,
Sergio Lopez
Cover local semantics, host/guest transfers, routing and queue
accounting. Synchronize capability-based skips and document manual
checks.
Signed-off-by: Sergio Lopez <slp@redhat.com>
---
tools/testing/vsock/Makefile | 3 +-
tools/testing/vsock/README | 23 +-
tools/testing/vsock/util.c | 62 +--
tools/testing/vsock/util.h | 10 +
tools/testing/vsock/vsock_test.c | 154 ++++++-
tools/testing/vsock/vsock_test_dgram.c | 809 +++++++++++++++++++++++++++++++++
tools/testing/vsock/vsock_test_dgram.h | 26 ++
7 files changed, 1055 insertions(+), 32 deletions(-)
diff --git a/tools/testing/vsock/Makefile b/tools/testing/vsock/Makefile
index 88211fd132d2..78e2cb7b1193 100644
--- a/tools/testing/vsock/Makefile
+++ b/tools/testing/vsock/Makefile
@@ -1,7 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-only
all: test vsock_perf
test: vsock_test vsock_diag_test vsock_uring_test
-vsock_test: vsock_test.o vsock_test_zerocopy.o timeout.o control.o util.o msg_zerocopy_common.o
+vsock_test: vsock_test.o vsock_test_zerocopy.o vsock_test_dgram.o \
+ timeout.o control.o util.o msg_zerocopy_common.o
vsock_diag_test: vsock_diag_test.o timeout.o control.o util.o
vsock_perf: vsock_perf.o msg_zerocopy_common.o
diff --git a/tools/testing/vsock/README b/tools/testing/vsock/README
index 680ce666ceb5..be34b032ef13 100644
--- a/tools/testing/vsock/README
+++ b/tools/testing/vsock/README
@@ -5,7 +5,7 @@ Hyper-V.
The following tests are available:
- * vsock_test - core AF_VSOCK socket functionality
+ * vsock_test - stream, seqpacket and datagram functionality
* vsock_diag_test - vsock_diag.ko module for listing open sockets
The following prerequisite steps are not automated and must be performed prior
@@ -32,7 +32,7 @@ Invoke test binaries in both directions as follows:
--control-port=1234 \
--peer-cid=2
(host)# $TEST_BINARY --mode=client \
- --control-port=$GUEST_IP \
+ --control-host=$GUEST_IP \
--control-port=1234 \
--peer-cid=3
@@ -51,6 +51,25 @@ suite run, e.g.
For more information see Documentation/dev-tools/kmemleak.rst.
+Datagram tests
+--------------
+vsock_test includes datagram cases in the client/server runs above. Unsupported
+cases print a skip reason.
+
+Virtio/vhost datagrams require kernel support on both sides and a VMM that
+negotiates VIRTIO_VSOCK_F_DGRAM. Load vsock_loopback on both host and guest for
+the local cases, and make /dev/vsock available. VMCI's dedicated datagram
+transport takes priority when loaded, causing virtio/vhost cases to skip.
+
+Run only the local datagram cases without a peer or TCP control connection:
+
+ ./vsock_test --local
+
+Use --list and --pick <test_id> to select individual cases. The BPF admission
+test (48, requiring BPF privileges) and nested routing test (49, requiring both
+virtio and vhost transports) need explicit selection. The VMCI queue-length
+test (53) runs only with VMCI.
+
vsock_perf utility
-------------------
'vsock_perf' is a simple tool to measure vsock performance. It works in
diff --git a/tools/testing/vsock/util.c b/tools/testing/vsock/util.c
index 299a4e8ecb63..6ad97403697f 100644
--- a/tools/testing/vsock/util.c
+++ b/tools/testing/vsock/util.c
@@ -499,47 +499,55 @@ void run_tests(const struct test_case *test_cases,
int i;
for (i = 0; test_cases[i].name; i++) {
+ const struct test_case *test = &test_cases[i];
void (*run)(const struct test_opts *opts);
+ const char *reason;
char *line;
- printf("%d - %s...", i, test_cases[i].name);
+ if (opts->local && !test->local)
+ continue;
+ printf("%d - %s...", i, test->name);
fflush(stdout);
- /* Full barrier before executing the next test. This
- * ensures that client and server are executing the
- * same test case. In particular, it means whoever is
- * faster will not see the peer still executing the
- * last test. This is important because port numbers
- * can be used by multiple test cases.
- */
- if (test_cases[i].skip)
- control_writeln("SKIP");
- else
- control_writeln("NEXT");
-
- line = control_readln();
- if (control_cmpln(line, "SKIP", false) || test_cases[i].skip) {
-
- printf("skipped\n");
-
+ if (opts->local) {
+ if (test->skip) {
+ printf("skipped\n");
+ continue;
+ }
+ } else {
+ /* Agree on explicit selection before any capability check
+ * or test can exchange messages on the control connection.
+ */
+ control_writeln(test->skip ? "SKIP" : "NEXT");
+ line = control_readln();
+ if (control_cmpln(line, "SKIP", false) || test->skip) {
+ printf("skipped\n");
+ free(line);
+ continue;
+ }
+ control_cmpln(line, "NEXT", true);
free(line);
- continue;
}
- control_cmpln(line, "NEXT", true);
- free(line);
-
- if (opts->mode == TEST_MODE_CLIENT)
- run = test_cases[i].run_client;
+ if (test->check) {
+ reason = test->check(opts);
+ if (reason) {
+ printf("skipped: %s\n", reason);
+ continue;
+ }
+ }
+ if (opts->local || opts->mode == TEST_MODE_CLIENT)
+ run = test->run_client;
else
- run = test_cases[i].run_server;
-
+ run = test->run_server;
if (run)
run(opts);
-
printf("ok\n");
}
+ if (opts->local)
+ return;
+
printf("All tests have been executed. Waiting other peer...");
fflush(stdout);
diff --git a/tools/testing/vsock/util.h b/tools/testing/vsock/util.h
index 9dabb547021e..e23290e7dcc6 100644
--- a/tools/testing/vsock/util.h
+++ b/tools/testing/vsock/util.h
@@ -51,6 +51,10 @@ struct test_opts {
enum test_mode mode;
unsigned int peer_cid;
unsigned int peer_port;
+ unsigned int bind_cid;
+ bool local;
+ bool to_host;
+ bool expect_to_host;
};
/* A test case definition. Test functions must print failures to stderr and
@@ -66,6 +70,12 @@ struct test_case {
void (*run_server)(const struct test_opts *opts);
bool skip;
+ bool local; /* Can run without a control connection */
+
+ /* Return a skip reason or NULL after the selection barrier. Checks
+ * that coordinate over the control channel must agree on both peers.
+ */
+ const char *(*check)(const struct test_opts *opts);
};
void init_signals(void);
diff --git a/tools/testing/vsock/vsock_test.c b/tools/testing/vsock/vsock_test.c
index 10b9074fb87f..f4ff6a42a9cb 100644
--- a/tools/testing/vsock/vsock_test.c
+++ b/tools/testing/vsock/vsock_test.c
@@ -27,6 +27,7 @@
#include <linux/sockios.h>
#include "vsock_test_zerocopy.h"
+#include "vsock_test_dgram.h"
#include "timeout.h"
#include "control.h"
#include "util.h"
@@ -2911,6 +2912,95 @@ static struct test_case test_cases[] = {
{
.name = "SO_INQ option validation",
.run_client = test_so_inq_validation_client,
+ },
+ .name = "SOCK_DGRAM autobind, duplicate bind and wildcard destination",
+ .run_client = test_dgram_bind,
+ .run_server = test_dgram_bind,
+ .check = vsock_dgram_check_local,
+ .local = true,
+ },
+ {
+ .name = "SOCK_DGRAM queue ioctls, poll, peek, truncation and empty messages",
+ .run_client = test_dgram_receive,
+ .run_server = test_dgram_receive,
+ .check = vsock_dgram_check_local,
+ .local = true,
+ },
+ {
+ .name = "SOCK_DGRAM unsupported receive flags preserve normal data",
+ .run_client = test_dgram_receive_flags,
+ .run_server = test_dgram_receive_flags,
+ .check = vsock_dgram_check_local,
+ .local = true,
+ },
+ {
+ .name = "SOCK_DGRAM socket filter trims receive length",
+ .run_client = test_dgram_filter,
+ .run_server = test_dgram_filter,
+ .check = vsock_dgram_check_local,
+ .local = true,
+ },
+ {
+ .name = "SOCK_DGRAM connect, disconnect and shutdown peer filtering",
+ .run_client = test_dgram_connect,
+ .run_server = test_dgram_connect,
+ .check = vsock_dgram_check_local,
+ .local = true,
+ },
+ {
+ .name = "SOCK_DGRAM explicit loopback source CIDs and reply addresses",
+ .run_client = test_dgram_source_cid,
+ .run_server = test_dgram_source_cid,
+ .check = vsock_dgram_check_local,
+ .local = true,
+ },
+ {
+ .name = "SOCK_DGRAM connected stream/seqpacket cannot shadow datagrams",
+ .run_client = test_dgram_bound_lookup,
+ .run_server = test_dgram_bound_lookup,
+ .check = vsock_dgram_check_local,
+ .local = true,
+ },
+ {
+ .name = "SOCK_DGRAM sockmap and sockhash reject datagrams (BPF privileges)",
+ .run_client = test_dgram_sockmap,
+ .run_server = test_dgram_sockmap,
+ .skip = true,
+ .check = vsock_dgram_check_local,
+ .local = true,
+ },
+ {
+ .name = "SOCK_DGRAM nested CID_HOST loopback rejection (nested VM only)",
+ .run_client = test_dgram_nested_source,
+ .run_server = test_dgram_nested_source,
+ .skip = true,
+ .check = vsock_dgram_check_local,
+ .local = true,
+ },
+ {
+ .name = "SOCK_DGRAM bidirectional sizes, 64 KiB boundaries and RX refill",
+ .run_client = test_dgram_sizes,
+ .run_server = test_dgram_sizes,
+ .check = vsock_dgram_check_peer,
+ },
+ {
+ .name = "SOCK_DGRAM connected socket alternates loopback and peer routes",
+ .run_client = test_dgram_routes,
+ .run_server = test_dgram_routes,
+ .check = vsock_dgram_check_routes,
+ },
+ {
+ .name = "SOCK_DGRAM peer filtering and disconnect across transports",
+ .run_client = test_dgram_peer_filter,
+ .run_server = test_dgram_peer_filter,
+ .check = vsock_dgram_check_peer,
+ },
+ {
+ .name = "SOCK_DGRAM VMCI ioctl(SIOCINQ) payload length",
+ .run_client = test_dgram_vmci_inq,
+ .run_server = test_dgram_vmci_inq,
+ .check = vsock_dgram_check_vmci,
+ .local = true,
},
{},
};
@@ -2957,6 +3047,26 @@ static const struct option longopts[] = {
.has_arg = required_argument,
.val = 't',
},
+ {
+ .name = "local",
+ .has_arg = no_argument,
+ .val = 'L',
+ },
+ {
+ .name = "bind-cid",
+ .has_arg = required_argument,
+ .val = 'b',
+ },
+ {
+ .name = "to-host",
+ .has_arg = no_argument,
+ .val = 'T',
+ },
+ {
+ .name = "expect-to-host",
+ .has_arg = no_argument,
+ .val = 'E',
+ },
{
.name = "help",
.has_arg = no_argument,
@@ -2972,8 +3082,10 @@ static void usage(void)
" Server: vsock_test --control-port=1234 --mode=server --peer-cid=3\n"
" Client: vsock_test --control-host=192.168.0.1 --control-port=1234 --mode=client --peer-cid=2\n"
"\n"
- "Run vsock.ko tests. Must be launched in both guest\n"
- "and host. One side must use --mode=client and\n"
+ " Local: vsock_test --local [--pick=<test_id>]\n"
+ "\n"
+ "Run vsock.ko tests. Paired tests must be launched in both guest\n"
+ "and host. One side must use --mode=client and\n"
"the other side must use --mode=server.\n"
"\n"
"A TCP control socket connection is used to coordinate tests\n"
@@ -2986,8 +3098,14 @@ static void usage(void)
"specified with --peer-port=<port> (or the default port)\n"
"and the next one.\n"
"\n"
+ "--local runs local datagram cases without a control connection.\n"
+ "\n"
"Options:\n"
" --help This help message\n"
+ " --local Run local datagram cases without a peer\n"
+ " --bind-cid <cid> Source CID for peer datagram tests [default: ANY]\n"
+ " --to-host Route peer datagrams through the frontend\n"
+ " --expect-to-host Expect the upstream datagram source flag\n"
" --control-host <host> Server IP address to connect to\n"
" --control-port <port> Server port to listen on/connect to\n"
" --mode client|server Server or client mode\n"
@@ -3011,7 +3129,10 @@ int main(int argc, char **argv)
.mode = TEST_MODE_UNSET,
.peer_cid = VMADDR_CID_ANY,
.peer_port = DEFAULT_PEER_PORT,
+ .bind_cid = VMADDR_CID_ANY,
};
+ bool picked = false;
+ size_t i;
srand(time(NULL));
init_signals();
@@ -3023,6 +3144,18 @@ int main(int argc, char **argv)
break;
switch (opt) {
+ case 'L':
+ opts.local = true;
+ break;
+ case 'b':
+ opts.bind_cid = parse_cid(optarg);
+ break;
+ case 'T':
+ opts.to_host = true;
+ break;
+ case 'E':
+ opts.expect_to_host = true;
+ break;
case 'H':
control_host = optarg;
break;
@@ -3055,6 +3188,7 @@ int main(int argc, char **argv)
case 't':
pick_test(test_cases, ARRAY_SIZE(test_cases) - 1,
optarg);
+ picked = true;
break;
case '?':
default:
@@ -3062,6 +3196,22 @@ int main(int argc, char **argv)
}
}
+ if (optind != argc)
+ usage();
+ if (opts.local) {
+ if (opts.mode != TEST_MODE_UNSET || control_host || control_port)
+ usage();
+ if (picked) {
+ for (i = 0; test_cases[i].name; i++) {
+ if (!test_cases[i].skip && !test_cases[i].local) {
+ fprintf(stderr, "test %zu requires paired mode\n", i);
+ exit(EXIT_FAILURE);
+ }
+ }
+ }
+ run_tests(test_cases, &opts);
+ return EXIT_SUCCESS;
+ }
if (!control_port)
usage();
if (opts.mode == TEST_MODE_UNSET)
diff --git a/tools/testing/vsock/vsock_test_dgram.c b/tools/testing/vsock/vsock_test_dgram.c
new file mode 100644
index 000000000000..d818f43176dc
--- /dev/null
+++ b/tools/testing/vsock/vsock_test_dgram.c
@@ -0,0 +1,809 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Datagram feature tests for vsock. */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/bpf.h>
+#include <linux/filter.h>
+#include <linux/sockios.h>
+#include <poll.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/syscall.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "control.h"
+#include "timeout.h"
+#include "vsock_test_dgram.h"
+
+#define MAX_DGRAM (64 * 1024)
+
+static void fail(const char *fmt, ...)
+{
+ va_list ap;
+
+ fprintf(stderr, "\nFAIL: ");
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+ exit(EXIT_FAILURE);
+}
+
+static void check(int ret, const char *operation)
+{
+ if (ret < 0)
+ fail("%s: %s", operation, strerror(errno));
+}
+
+static void expect_error(ssize_t ret, int error, const char *operation)
+{
+ if (ret != -1 || errno != error)
+ fail("%s: expected %s, got %zd (%s)", operation,
+ strerror(error), ret, ret == -1 ? strerror(errno) : "success");
+}
+
+static int64_t now_ms(void)
+{
+ struct timespec ts;
+
+ check(clock_gettime(CLOCK_MONOTONIC, &ts), "clock_gettime");
+ return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
+}
+
+static short poll_fd(int fd, short events, int milliseconds)
+{
+ struct pollfd pfd = { .fd = fd, .events = events };
+ int ret;
+
+ do {
+ ret = poll(&pfd, 1, milliseconds);
+ } while (ret < 0 && errno == EINTR);
+ check(ret, "poll");
+ if (pfd.revents & (POLLERR | POLLNVAL))
+ fail("unexpected poll events %#x", pfd.revents);
+ return pfd.revents;
+}
+
+static void wait_readable(int fd)
+{
+ if (!(poll_fd(fd, POLLIN, TIMEOUT * 1000) & POLLIN))
+ fail("waiting for socket readability timed out");
+}
+
+static struct sockaddr_vm address(unsigned int cid, unsigned int port)
+{
+ return (struct sockaddr_vm) {
+ .svm_family = AF_VSOCK,
+ .svm_cid = cid,
+ .svm_port = port,
+ };
+}
+
+static struct sockaddr_vm sock_name(int fd, bool peer)
+{
+ struct sockaddr_vm addr;
+ socklen_t len = sizeof(addr);
+ int ret;
+
+ if (peer)
+ ret = getpeername(fd, (struct sockaddr *)&addr, &len);
+ else
+ ret = getsockname(fd, (struct sockaddr *)&addr, &len);
+ check(ret, peer ? "getpeername" : "getsockname");
+ if (len != sizeof(addr) || addr.svm_family != AF_VSOCK)
+ fail("invalid socket address");
+ return addr;
+}
+
+static unsigned int local_cid(void)
+{
+ unsigned int cid;
+ int fd;
+
+ fd = open("/dev/vsock", O_RDONLY | O_CLOEXEC);
+ check(fd, "open /dev/vsock");
+ check(ioctl(fd, IOCTL_VM_SOCKETS_GET_LOCAL_CID, &cid), "get local CID");
+ close(fd);
+ if (cid == VMADDR_CID_ANY || cid <= VMADDR_CID_LOCAL)
+ fail("a virtio or vhost transport is required (local CID %u)", cid);
+ return cid;
+}
+
+static void bind_fd(int fd, unsigned int cid, unsigned int port)
+{
+ struct sockaddr_vm addr = address(cid, port);
+
+ check(bind(fd, (struct sockaddr *)&addr, sizeof(addr)), "bind");
+}
+
+static int dgram_socket(void)
+{
+ int fd = socket(AF_VSOCK, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
+ int rcvbuf = 1024 * 1024;
+
+ check(fd, "socket(AF_VSOCK, SOCK_DGRAM): check transport/feature support");
+ check(setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)), "SO_RCVBUF");
+ return fd;
+}
+
+enum dgram_capability {
+ DGRAM_SOCKET = 1 << 0,
+ DGRAM_VIRTIO = 1 << 1,
+ DGRAM_VMCI = 1 << 2,
+ DGRAM_LOOPBACK = 1 << 3,
+ DGRAM_PEER = 1 << 4,
+};
+
+static bool dgram_unavailable(int error)
+{
+ return error == EAFNOSUPPORT || error == EPROTONOSUPPORT ||
+ error == ESOCKTNOSUPPORT || error == ENODEV;
+}
+
+static unsigned int dgram_capabilities(const struct test_opts *opts,
+ unsigned int needs)
+{
+ unsigned int caps = DGRAM_SOCKET;
+ struct sockaddr_vm peer;
+ int fd, local_fd;
+
+ fd = socket(AF_VSOCK, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
+ if (fd < 0) {
+ if (dgram_unavailable(errno))
+ return 0;
+ check(fd, "probe datagram socket");
+ }
+ /* VMCI registers the dedicated datagram transport when loaded. */
+ if (!access("/sys/module/vmw_vsock_vmci_transport", F_OK))
+ caps |= DGRAM_VMCI;
+ else
+ caps |= DGRAM_VIRTIO;
+
+ if ((needs & DGRAM_PEER) && (caps & DGRAM_VIRTIO)) {
+ bind_fd(fd, opts->bind_cid, VMADDR_PORT_ANY);
+ peer = address(opts->peer_cid, opts->peer_port);
+ if (opts->to_host)
+ peer.svm_flags = VMADDR_FLAG_TO_HOST;
+ if (!connect(fd, (struct sockaddr *)&peer, sizeof(peer)))
+ caps |= DGRAM_PEER;
+ else if (!dgram_unavailable(errno) && errno != EINVAL)
+ check(-1, "probe peer datagram route");
+ }
+ close(fd);
+
+ if ((needs & DGRAM_LOOPBACK) && (caps & DGRAM_VIRTIO)) {
+ /* Binding a stream socket to CID_LOCAL probes registration of
+ * the loopback transport without sending a packet to the host.
+ */
+ local_fd = vsock_bind_try(VMADDR_CID_LOCAL, VMADDR_PORT_ANY, SOCK_STREAM);
+ if (local_fd >= 0) {
+ caps |= DGRAM_LOOPBACK;
+ close(local_fd);
+ } else if (errno != EADDRNOTAVAIL) {
+ check(local_fd, "probe loopback transport");
+ }
+ }
+ return caps;
+}
+
+static const char *dgram_check(const struct test_opts *opts, unsigned int needs)
+{
+ unsigned int local = dgram_capabilities(opts, needs);
+ unsigned int both = local;
+
+ /* run_tests() has already synchronized explicit --pick/--skip
+ * selection. Both peers can now safely exchange capabilities.
+ */
+ if (!opts->local) {
+ control_writeulong(local);
+ both &= control_readulong();
+ }
+ if (!(both & DGRAM_SOCKET))
+ return local & DGRAM_SOCKET ?
+ "datagrams unavailable at peer" : "datagrams unavailable locally";
+ if ((needs & DGRAM_VIRTIO) && !(both & DGRAM_VIRTIO))
+ return "requires virtio/vhost datagram transports on both peers";
+ if ((needs & DGRAM_VMCI) && !(both & DGRAM_VMCI))
+ return "requires VMCI datagram transports on both peers";
+ if ((needs & DGRAM_LOOPBACK) && !(both & DGRAM_LOOPBACK))
+ return "load vsock_loopback on both peers";
+ if ((needs & DGRAM_PEER) && !(both & DGRAM_PEER))
+ return "peer route does not support datagrams";
+ return NULL;
+}
+
+const char *vsock_dgram_check_local(const struct test_opts *opts)
+{
+ return dgram_check(opts, DGRAM_VIRTIO | DGRAM_LOOPBACK);
+}
+
+const char *vsock_dgram_check_peer(const struct test_opts *opts)
+{
+ return dgram_check(opts, DGRAM_VIRTIO | DGRAM_PEER);
+}
+
+const char *vsock_dgram_check_routes(const struct test_opts *opts)
+{
+ return dgram_check(opts, DGRAM_VIRTIO | DGRAM_PEER | DGRAM_LOOPBACK);
+}
+
+const char *vsock_dgram_check_vmci(const struct test_opts *opts)
+{
+ return dgram_check(opts, DGRAM_VMCI);
+}
+
+static int bound_socket(unsigned int cid)
+{
+ int fd = dgram_socket();
+
+ bind_fd(fd, cid, VMADDR_PORT_ANY);
+ return fd;
+}
+
+static struct sockaddr_vm local_address(int fd)
+{
+ struct sockaddr_vm addr = sock_name(fd, false);
+
+ addr.svm_cid = VMADDR_CID_LOCAL;
+ return addr;
+}
+
+static void connect_fd(int fd, const struct sockaddr_vm *addr)
+{
+ check(connect(fd, (const struct sockaddr *)addr, sizeof(*addr)), "connect");
+}
+
+static void disconnect_fd(int fd)
+{
+ struct sockaddr_vm addr = { .svm_family = AF_UNSPEC };
+ socklen_t len = sizeof(addr);
+
+ check(connect(fd, (struct sockaddr *)&addr, sizeof(addr)), "disconnect");
+ expect_error(getpeername(fd, (struct sockaddr *)&addr, &len),
+ ENOTCONN, "getpeername after disconnect");
+}
+
+static int queue_bytes(int fd, unsigned long op)
+{
+ int bytes;
+
+ check(ioctl(fd, op, &bytes), "queue ioctl");
+ if (bytes < 0)
+ fail("negative queue length: %d", bytes);
+ return bytes;
+}
+
+static void expect_inq(int fd, int expected)
+{
+ int actual = queue_bytes(fd, SIOCINQ);
+
+ if (actual != expected)
+ fail("SIOCINQ: expected %d, got %d", expected, actual);
+}
+
+static void wait_sent(int fd)
+{
+ int64_t deadline = now_ms() + TIMEOUT * 1000;
+
+ while (queue_bytes(fd, SIOCOUTQ)) {
+ if (now_ms() >= deadline)
+ fail("SIOCOUTQ did not return to zero");
+ usleep(1000);
+ }
+}
+
+static void send_packet(int fd, const struct sockaddr_vm *addr,
+ const void *buf, size_t len)
+{
+ int64_t deadline = now_ms() + TIMEOUT * 1000;
+ ssize_t ret;
+
+ for (;;) {
+ ret = sendto(fd, buf, len, MSG_DONTWAIT | MSG_NOSIGNAL,
+ (const struct sockaddr *)addr, addr ? sizeof(*addr) : 0);
+ if (ret >= 0)
+ break;
+ if (errno != EAGAIN && errno != EINTR)
+ fail("sendto(%zu bytes): %s", len, strerror(errno));
+ if (now_ms() >= deadline)
+ fail("sendto(%zu bytes) timed out", len);
+ poll_fd(fd, POLLOUT, 1);
+ }
+ if (ret != len)
+ fail("partial datagram send: %zd/%zu", ret, len);
+}
+
+static struct sockaddr_vm recv_packet(int fd, void *buf, size_t capacity,
+ int flags, size_t expected, bool truncated)
+{
+ struct sockaddr_vm src = { 0 };
+ struct iovec iov = { .iov_base = buf, .iov_len = capacity };
+ struct msghdr msg = {
+ .msg_name = &src,
+ .msg_namelen = sizeof(src),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ };
+ ssize_t ret;
+
+ wait_readable(fd);
+ ret = recvmsg(fd, &msg, flags | MSG_DONTWAIT);
+ check(ret, "recvmsg");
+ if (ret != expected || !!(msg.msg_flags & MSG_TRUNC) != truncated)
+ fail("recvmsg: expected %zu bytes, trunc=%d; got %zd, flags=%#x",
+ expected, truncated, ret, msg.msg_flags);
+ if (msg.msg_namelen != sizeof(src) || src.svm_family != AF_VSOCK)
+ fail("invalid recvmsg source address");
+ return src;
+}
+
+static void expect_empty(int fd)
+{
+ char byte;
+
+ if (poll_fd(fd, POLLIN, 0) & POLLIN)
+ fail("unexpected queued datagram");
+ expect_error(recv(fd, &byte, 1, MSG_DONTWAIT), EAGAIN, "empty recv");
+ expect_inq(fd, 0);
+}
+
+void test_dgram_bind(const struct test_opts *opts)
+{
+ int rx = bound_socket(VMADDR_CID_ANY);
+ struct sockaddr_vm dest = local_address(rx);
+ int tx = dgram_socket();
+ int other = dgram_socket();
+ struct sockaddr_vm src;
+ char byte;
+
+ dest.svm_cid = VMADDR_CID_ANY;
+ expect_error(bind(other, (struct sockaddr *)&dest, sizeof(dest)),
+ EADDRINUSE, "duplicate bind");
+ send_packet(tx, &dest, "A", 1);
+ src = recv_packet(rx, &byte, 1, 0, 1, false);
+ if (byte != 'A' || src.svm_port != sock_name(tx, false).svm_port ||
+ src.svm_cid != VMADDR_CID_LOCAL || src.svm_port < 1024)
+ fail("autobind or wildcard destination returned an invalid source");
+ close(rx);
+ bind_fd(other, VMADDR_CID_ANY, dest.svm_port);
+ close(other);
+ close(tx);
+}
+
+void test_dgram_receive(const struct test_opts *opts)
+{
+ int rx = bound_socket(VMADDR_CID_ANY);
+ int tx = bound_socket(VMADDR_CID_ANY);
+ struct sockaddr_vm dest = local_address(rx);
+ char buf[80];
+ char first[31], second[79];
+
+ memset(first, 'A', sizeof(first));
+ memset(second, 'B', sizeof(second));
+ expect_empty(rx);
+ if (!(poll_fd(tx, POLLOUT, 0) & POLLOUT))
+ fail("new socket is not writable");
+ if (queue_bytes(tx, SIOCOUTQ))
+ fail("new socket has pending output");
+ send_packet(tx, &dest, first, sizeof(first));
+ send_packet(tx, &dest, second, sizeof(second));
+ wait_readable(rx);
+ expect_inq(rx, sizeof(first));
+ recv_packet(rx, buf, 5, MSG_PEEK, 5, true);
+ if (memcmp(buf, first, 5))
+ fail("MSG_PEEK payload mismatch");
+ expect_inq(rx, sizeof(first));
+ recv_packet(rx, buf, 5, MSG_TRUNC, sizeof(first), true);
+ wait_readable(rx);
+ expect_inq(rx, sizeof(second));
+ recv_packet(rx, buf, sizeof(buf), 0, sizeof(second), false);
+ if (memcmp(buf, second, sizeof(second)))
+ fail("datagram boundary or payload mismatch");
+ send_packet(tx, &dest, "", 0);
+ wait_readable(rx);
+ expect_inq(rx, 0);
+ recv_packet(rx, buf, sizeof(buf), MSG_PEEK, 0, false);
+ recv_packet(rx, buf, sizeof(buf), 0, 0, false);
+ send_packet(tx, &dest, first, sizeof(first));
+ recv_packet(rx, buf, 0, MSG_TRUNC, sizeof(first), true);
+ expect_empty(rx);
+ wait_sent(tx);
+ close(tx);
+ close(rx);
+}
+
+void test_dgram_receive_flags(const struct test_opts *opts)
+{
+ int rx = bound_socket(VMADDR_CID_ANY);
+ int tx = bound_socket(VMADDR_CID_ANY);
+ struct sockaddr_vm dest = local_address(rx);
+ const int flags[] = { MSG_OOB, MSG_ERRQUEUE };
+ char byte;
+ size_t i;
+
+ send_packet(tx, &dest, "A", 1);
+ wait_readable(rx);
+ for (i = 0; i < ARRAY_SIZE(flags); i++) {
+ expect_error(recv(rx, &byte, 1, flags[i] | MSG_DONTWAIT),
+ EOPNOTSUPP, "unsupported receive flag");
+ expect_inq(rx, 1);
+ }
+ recv_packet(rx, &byte, 1, 0, 1, false);
+ if (byte != 'A')
+ fail("unsupported flags consumed the normal packet");
+ close(tx);
+ close(rx);
+}
+
+void test_dgram_vmci_inq(const struct test_opts *opts)
+{
+ int rx = bound_socket(VMADDR_CID_ANY);
+ int tx = bound_socket(VMADDR_CID_ANY);
+ struct sockaddr_vm dest = sock_name(rx, false);
+ char first[31], second[79], buf[80];
+
+ memset(first, 'A', sizeof(first));
+ memset(second, 'B', sizeof(second));
+ /* CID_ANY is resolved by the dedicated transport to its local CID. */
+ dest.svm_cid = VMADDR_CID_ANY;
+ send_packet(tx, &dest, first, sizeof(first));
+ send_packet(tx, &dest, second, sizeof(second));
+ wait_readable(rx);
+ expect_inq(rx, sizeof(first));
+ recv_packet(rx, buf, sizeof(buf), 0, sizeof(first), false);
+ if (memcmp(buf, first, sizeof(first)))
+ fail("VMCI first payload mismatch");
+ wait_readable(rx);
+ expect_inq(rx, sizeof(second));
+ recv_packet(rx, buf, sizeof(buf), 0, sizeof(second), false);
+ if (memcmp(buf, second, sizeof(second)))
+ fail("VMCI second payload mismatch");
+ send_packet(tx, &dest, "", 0);
+ wait_readable(rx);
+ expect_inq(rx, 0);
+ recv_packet(rx, buf, sizeof(buf), 0, 0, false);
+ expect_empty(rx);
+ close(tx);
+ close(rx);
+}
+
+void test_dgram_filter(const struct test_opts *opts)
+{
+ struct sock_filter insn[] = { BPF_STMT(BPF_RET | BPF_K, 7) };
+ struct sock_fprog prog = { .len = ARRAY_SIZE(insn), .filter = insn };
+ int rx = bound_socket(VMADDR_CID_ANY);
+ int tx = bound_socket(VMADDR_CID_ANY);
+ struct sockaddr_vm dest = local_address(rx);
+ char data[100], buf[100];
+
+ memset(data, 'F', sizeof(data));
+ check(setsockopt(rx, SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog)),
+ "SO_ATTACH_FILTER");
+ send_packet(tx, &dest, data, sizeof(data));
+ wait_readable(rx);
+ expect_inq(rx, 7);
+ recv_packet(rx, buf, 1, MSG_PEEK | MSG_TRUNC, 7, true);
+ expect_inq(rx, 7);
+ memset(buf, 0, sizeof(buf));
+ recv_packet(rx, buf, sizeof(buf), MSG_TRUNC, 7, false);
+ if (memcmp(buf, data, 7) || buf[7])
+ fail("filtered receive copied beyond the filtered payload");
+ send_packet(tx, &dest, data, sizeof(data));
+ recv_packet(rx, buf, 1, MSG_TRUNC, 7, true);
+ expect_empty(rx);
+ close(tx);
+ close(rx);
+}
+
+static void check_peer_filter(int rx, int allowed, int stray)
+{
+ struct sockaddr_vm dest = local_address(rx);
+ char byte;
+
+ /* Both packets use the same ordered loopback worker. */
+ send_packet(stray, &dest, "B", 1);
+ send_packet(allowed, &dest, "A", 1);
+ recv_packet(rx, &byte, 1, 0, 1, false);
+ if (byte != 'A')
+ fail("connected socket accepted a different peer");
+ expect_empty(rx);
+}
+
+void test_dgram_connect(const struct test_opts *opts)
+{
+ int rx = bound_socket(VMADDR_CID_ANY);
+ int allowed = bound_socket(VMADDR_CID_ANY);
+ int stray = bound_socket(VMADDR_CID_ANY);
+ struct sockaddr_vm peer = local_address(allowed);
+ struct sockaddr_vm dest = local_address(rx);
+ char byte;
+
+ connect_fd(rx, &peer);
+ check_peer_filter(rx, allowed, stray);
+ disconnect_fd(rx);
+ send_packet(stray, &dest, "B", 1);
+ recv_packet(rx, &byte, 1, 0, 1, false);
+ if (byte != 'B')
+ fail("disconnect did not remove peer filtering");
+ connect_fd(rx, &peer);
+ check(shutdown(rx, SHUT_WR), "shutdown SHUT_WR");
+ if (poll_fd(rx, POLLOUT, 0) & POLLOUT)
+ fail("write-shutdown socket is still writable");
+ check_peer_filter(rx, allowed, stray);
+ check(shutdown(rx, SHUT_RD), "shutdown SHUT_RD");
+ if (!(poll_fd(rx, POLLIN | POLLRDHUP, 0) & POLLRDHUP))
+ fail("read shutdown did not report POLLRDHUP");
+ close(stray);
+ close(allowed);
+ close(rx);
+}
+
+void test_dgram_source_cid(const struct test_opts *opts)
+{
+ unsigned int cids[] = { VMADDR_CID_LOCAL, local_cid() };
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(cids); i++) {
+ int rx = bound_socket(VMADDR_CID_ANY);
+ int tx = bound_socket(cids[i]);
+ struct sockaddr_vm dest = local_address(rx);
+ struct sockaddr_vm src;
+ char byte;
+
+ send_packet(tx, &dest, "A", 1);
+ src = recv_packet(rx, &byte, 1, 0, 1, false);
+ if (src.svm_cid != cids[i] || src.svm_flags ||
+ src.svm_port != sock_name(tx, false).svm_port)
+ fail("explicit loopback source CID %u was not preserved", cids[i]);
+ send_packet(rx, &src, "R", 1);
+ recv_packet(tx, &byte, 1, 0, 1, false);
+ if (byte != 'R')
+ fail("reply to explicit source CID failed");
+ close(tx);
+ close(rx);
+ }
+}
+
+void test_dgram_bound_lookup(const struct test_opts *opts)
+{
+ const int types[] = { SOCK_STREAM, SOCK_SEQPACKET };
+ unsigned int cid = local_cid();
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(types); i++) {
+ int listener = vsock_bind(VMADDR_CID_ANY, VMADDR_PORT_ANY, types[i]);
+ int stream = vsock_bind(VMADDR_CID_LOCAL, VMADDR_PORT_ANY, types[i]);
+ struct sockaddr_vm listen_addr = local_address(listener);
+ struct sockaddr_vm src = sock_name(stream, false);
+ struct sockaddr_vm dest;
+ int accepted, tx, rx;
+ char byte;
+
+ check(listen(listener, 1), "listen");
+ if (vsock_connect_fd(stream, listen_addr.svm_cid, listen_addr.svm_port))
+ fail("%s loopback connect: %s",
+ types[i] == SOCK_STREAM ? "stream" : "seqpacket", strerror(errno));
+ wait_readable(listener);
+ accepted = accept4(listener, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC);
+ check(accepted, "accept");
+ if (sock_name(accepted, true).svm_cid != VMADDR_CID_LOCAL ||
+ sock_name(accepted, true).svm_port != src.svm_port)
+ fail("unexpected accepted loopback peer");
+ close(listener);
+ rx = dgram_socket();
+ bind_fd(rx, cid, src.svm_port);
+ dest = sock_name(rx, false);
+ tx = dgram_socket();
+ /* Reuse the listener's port for the datagram source. The receive
+ * socket uses the stream client's port at another local CID.
+ * Connected lookup ignores the destination CID, so it would find
+ * the stream client before the bound datagram socket.
+ */
+ bind_fd(tx, VMADDR_CID_LOCAL, listen_addr.svm_port);
+ send_packet(tx, &dest, "D", 1);
+ recv_packet(rx, &byte, 1, 0, 1, false);
+ if (byte != 'D' || (poll_fd(stream, POLLIN, 0) & POLLIN) ||
+ (poll_fd(accepted, POLLIN, 0) & POLLIN))
+ fail("connected socket shadowed the reused datagram port");
+ send_byte(stream, 1, 0);
+ wait_readable(accepted);
+ recv_byte(accepted, 1, 0);
+ send_byte(accepted, 1, 0);
+ wait_readable(stream);
+ recv_byte(stream, 1, 0);
+ close(tx);
+ close(rx);
+ close(accepted);
+ close(stream);
+ }
+}
+
+void test_dgram_sockmap(const struct test_opts *opts)
+{
+ const int types[] = { BPF_MAP_TYPE_SOCKMAP, BPF_MAP_TYPE_SOCKHASH };
+ int fd = bound_socket(VMADDR_CID_ANY);
+ struct sockaddr_vm addr = local_address(fd);
+ uint64_t value = fd;
+ uint32_t key = 0;
+ size_t i;
+
+ connect_fd(fd, &addr);
+ for (i = 0; i < ARRAY_SIZE(types); i++) {
+ union bpf_attr attr = {
+ .map_type = types[i],
+ .key_size = sizeof(key),
+ .value_size = sizeof(value),
+ .max_entries = 1,
+ };
+ int map = syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));
+
+ check(map, "BPF_MAP_CREATE (requires BPF privileges and CONFIG_BPF_SYSCALL)");
+ memset(&attr, 0, sizeof(attr));
+ attr.map_fd = map;
+ attr.key = (uintptr_t)&key;
+ attr.value = (uintptr_t)&value;
+ attr.flags = BPF_ANY;
+ expect_error(syscall(__NR_bpf, BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)),
+ EOPNOTSUPP, "datagram sockmap admission");
+ close(map);
+ }
+ close(fd);
+}
+
+void test_dgram_nested_source(const struct test_opts *opts)
+{
+ int rx, tx;
+ struct sockaddr_vm dest;
+
+ if (local_cid() <= VMADDR_CID_HOST)
+ fail("this test requires both a guest frontend and vhost_vsock");
+ rx = bound_socket(VMADDR_CID_LOCAL);
+ tx = bound_socket(VMADDR_CID_HOST);
+ dest = local_address(rx);
+ expect_error(sendto(tx, "A", 1, MSG_NOSIGNAL,
+ (struct sockaddr *)&dest, sizeof(dest)),
+ EADDRNOTAVAIL, "nested CID_HOST loopback send");
+ expect_error(connect(tx, (struct sockaddr *)&dest, sizeof(dest)),
+ EADDRNOTAVAIL, "nested CID_HOST loopback connect");
+ if (queue_bytes(tx, SIOCOUTQ))
+ fail("rejected route left pending output");
+ expect_empty(rx);
+ close(tx);
+ close(rx);
+}
+
+static int peer_socket(const struct test_opts *opts, struct sockaddr_vm *peer)
+{
+ int fd = bound_socket(opts->bind_cid);
+
+ control_writeulong(sock_name(fd, false).svm_port);
+ *peer = address(opts->peer_cid, control_readulong());
+ if (opts->to_host)
+ peer->svm_flags = VMADDR_FLAG_TO_HOST;
+ return fd;
+}
+
+static void check_source(const struct test_opts *opts,
+ const struct sockaddr_vm *peer, const struct sockaddr_vm *src)
+{
+ bool upstream = opts->expect_to_host || opts->peer_cid == VMADDR_CID_HOST;
+
+ if (src->svm_cid != peer->svm_cid || src->svm_port != peer->svm_port ||
+ src->svm_flags != (upstream ? VMADDR_FLAG_TO_HOST : 0))
+ fail("source expected %u:%u flags=%u, got %u:%u flags=%u",
+ peer->svm_cid, peer->svm_port, upstream ? VMADDR_FLAG_TO_HOST : 0,
+ src->svm_cid, src->svm_port, src->svm_flags);
+}
+
+static void fill_payload(char *buf, size_t len, unsigned int seed)
+{
+ size_t i;
+
+ for (i = 0; i < len; i++)
+ buf[i] = (i * 131 + seed) & 0xff;
+}
+
+void test_dgram_sizes(const struct test_opts *opts)
+{
+ const size_t sizes[] = { 0, 1, 7, 255, 4095, 4096, 4097,
+ 8192, 32768, 65535, MAX_DGRAM };
+ char *data = malloc(MAX_DGRAM + 1);
+ char *buf = malloc(MAX_DGRAM + 1);
+ struct sockaddr_vm peer, src;
+ unsigned int round;
+ int fd;
+ size_t i;
+
+ if (!data || !buf)
+ fail("malloc");
+ fd = peer_socket(opts, &peer);
+ fill_payload(data, MAX_DGRAM + 1, 0);
+ expect_error(sendto(fd, data, MAX_DGRAM + 1, MSG_NOSIGNAL,
+ (struct sockaddr *)&peer, sizeof(peer)),
+ EMSGSIZE, "oversized datagram");
+ if (queue_bytes(fd, SIOCOUTQ))
+ fail("oversized datagram left pending output");
+ /* More than a default RX ring's worth, including on small rings. */
+ for (round = 0; round < 32; round++) {
+ for (i = 0; i < ARRAY_SIZE(sizes); i++) {
+ fill_payload(data, sizes[i], round * ARRAY_SIZE(sizes) + i);
+ if (opts->mode == TEST_MODE_CLIENT)
+ send_packet(fd, &peer, data, sizes[i]);
+ src = recv_packet(fd, buf, MAX_DGRAM + 1, 0, sizes[i], false);
+ check_source(opts, &peer, &src);
+ if (memcmp(data, buf, sizes[i]))
+ fail("payload mismatch: size %zu, round %u", sizes[i], round);
+ if (opts->mode == TEST_MODE_SERVER)
+ send_packet(fd, &src, buf, sizes[i]);
+ }
+ }
+ wait_sent(fd);
+ expect_empty(fd);
+ close(fd);
+ free(buf);
+ free(data);
+}
+
+void test_dgram_routes(const struct test_opts *opts)
+{
+ struct sockaddr_vm peer, src, stored, local;
+ int fd = peer_socket(opts, &peer);
+ int aux = bound_socket(VMADDR_CID_ANY);
+ unsigned int round;
+ char byte;
+
+ connect_fd(fd, &peer);
+ stored = sock_name(fd, true);
+ local = local_address(aux);
+ for (round = 0; round < 32; round++) {
+ send_packet(fd, &local, "L", 1);
+ recv_packet(aux, &byte, 1, 0, 1, false);
+ if (byte != 'L')
+ fail("sendto on a connected socket did not use loopback");
+ src = sock_name(fd, true);
+ if (src.svm_cid != stored.svm_cid || src.svm_port != stored.svm_port ||
+ src.svm_flags != stored.svm_flags)
+ fail("per-message route changed the connected peer");
+ send_packet(fd, NULL, "P", 1);
+ src = recv_packet(fd, &byte, 1, 0, 1, false);
+ check_source(opts, &peer, &src);
+ if (byte != 'P')
+ fail("connected send did not retain the peer route");
+ }
+ wait_sent(fd);
+ close(aux);
+ close(fd);
+}
+
+void test_dgram_peer_filter(const struct test_opts *opts)
+{
+ struct sockaddr_vm peer, src;
+ int fd = peer_socket(opts, &peer);
+ int stray = bound_socket(opts->bind_cid);
+ char byte;
+
+ connect_fd(fd, &peer);
+ control_writeln("CONNECTED");
+ control_expectln("CONNECTED");
+ send_packet(stray, &peer, "B", 1);
+ send_packet(fd, NULL, "A", 1);
+ src = recv_packet(fd, &byte, 1, 0, 1, false);
+ check_source(opts, &peer, &src);
+ if (byte != 'A')
+ fail("connected socket accepted a different peer port");
+ disconnect_fd(fd);
+ control_writeln("DISCONNECTED");
+ control_expectln("DISCONNECTED");
+ send_packet(stray, &peer, "B", 1);
+ src = recv_packet(fd, &byte, 1, 0, 1, false);
+ if (byte != 'B' || src.svm_cid != peer.svm_cid || src.svm_port == peer.svm_port)
+ fail("disconnected socket did not accept the new peer");
+ close(stray);
+ close(fd);
+}
diff --git a/tools/testing/vsock/vsock_test_dgram.h b/tools/testing/vsock/vsock_test_dgram.h
new file mode 100644
index 000000000000..0dec74ef5f46
--- /dev/null
+++ b/tools/testing/vsock/vsock_test_dgram.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef VSOCK_TEST_DGRAM_H
+#define VSOCK_TEST_DGRAM_H
+
+#include "util.h"
+
+const char *vsock_dgram_check_local(const struct test_opts *opts);
+const char *vsock_dgram_check_peer(const struct test_opts *opts);
+const char *vsock_dgram_check_routes(const struct test_opts *opts);
+const char *vsock_dgram_check_vmci(const struct test_opts *opts);
+
+void test_dgram_bind(const struct test_opts *opts);
+void test_dgram_receive(const struct test_opts *opts);
+void test_dgram_receive_flags(const struct test_opts *opts);
+void test_dgram_filter(const struct test_opts *opts);
+void test_dgram_connect(const struct test_opts *opts);
+void test_dgram_source_cid(const struct test_opts *opts);
+void test_dgram_bound_lookup(const struct test_opts *opts);
+void test_dgram_sockmap(const struct test_opts *opts);
+void test_dgram_nested_source(const struct test_opts *opts);
+void test_dgram_sizes(const struct test_opts *opts);
+void test_dgram_routes(const struct test_opts *opts);
+void test_dgram_peer_filter(const struct test_opts *opts);
+void test_dgram_vmci_inq(const struct test_opts *opts);
+
+#endif /* VSOCK_TEST_DGRAM_H */
--
2.56.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH RFC 0/8] vsock: add datagram support to virtio and vhost
2026-10-28 16:04 [PATCH RFC 0/8] vsock: add datagram support to virtio and vhost Sergio Lopez
` (7 preceding siblings ...)
2026-10-28 16:04 ` [PATCH RFC 8/8] vsock: add datagram tests to vsock_test Sergio Lopez
@ 2026-10-28 17:25 ` Alexei Starovoitov
8 siblings, 0 replies; 10+ messages in thread
From: Alexei Starovoitov @ 2026-10-28 17:25 UTC (permalink / raw)
To: Sergio Lopez, Stefano Garzarella, Bobby Eshleman,
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
Cc: virtualization, netdev, linux-kernel, kvm, bpf, linux-hyperv
On Wed, Oct 28, 2026 at 09:04 AM Sergio Lopez <slp@redhat.com> wrote:
> This series implements SOCK_DGRAM support for virtio-vsock and
> vhost-vsock, allowing applications to exchange connectionless messages
> through the existing AF_VSOCK datagram API.
The cover doesn't say who needs it.
Bobby and Amery took the same feature to v6:
https://lore.kernel.org/all/20240710212555.1617795-1-amery.hung@bytedance.com/
Stefano said there that it can work well and that the virtio spec change
is the next step. The last mails of the thread have Amery's v7 WIP and a
short list of what is left.
The cover doesn't mention it. The spec change is still missing.
Why not finish v7 instead ?
This RFC goes the other way in these places:
what v6 this RFC
----------------- ------------------------ ----------------------------
(cid, port) space own bind table for dgram shared with stream. The 2nd
bind() gets EADDRINUSE
vsk->transport empty until connect(), g2h or h2g, picked in
lookup per packet socket(). -ENODEV with
vsock_loopback only
routing one helper for stream 2nd copy of the chain in
and dgram vsock_assign_transport()
recvmsg() in af_vsock.c, dgram_dequeue() stays,
no dgram_dequeue() dgram_payload_len() is new
VMCI fallback first. No virtio dgram when
its module is loaded
dgram > RX buffer drop, EHOSTUNREACH in every RX buffer is 64K
the error queue
> Datagrams use destination-based routing across guest-to-host,
> host-to-guest and loopback transports while retaining the socket's
> transport state. VMCI keeps priority as the dedicated datagram
A dgram socket uses two members of struct virtio_vsock_sock: tx_lock and
bytes_unsent. They serve SIOCOUTQ only.
virtio_transport_alloc_skb() does skb_set_owner_w() already.
udp_ioctl() returns sk_wmem_alloc_get() for SIOCOUTQ.
Without them a dgram socket has no transport state, like in v6.
Then no need for the owner transport, dgram_transport_match(),
fallback_transport, tx_accounted, the skb destructor, etc.
> transport. Guest RX buffers accommodate complete messages when
> datagrams are negotiated, preserving message boundaries.
That's 64K per RX buffer instead of 4K, for stream and seqpacket too.
It undoes commit 03a92f036a04 ("vsock/virtio: Resize receive buffers so
that each SKB fits in a 4K page").
pskb_trim_unique() doesn't reduce skb->truesize. So a datagram of 1 byte
charges more than 64K to sk_rmem_alloc of the guest socket.
The default sk_rcvbuf is about 208K on x86-64. The 5th datagram in the
queue is dropped.
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-10-28 17:25 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-28 16:04 [PATCH RFC 0/8] vsock: add datagram support to virtio and vhost Sergio Lopez
2026-10-28 16:04 ` [PATCH RFC 1/8] vsock: expose the core port allocator to transports Sergio Lopez
2026-10-28 16:04 ` [PATCH RFC 2/8] vsock: report datagram receive and send queue state Sergio Lopez
2026-10-28 16:04 ` [PATCH RFC 3/8] vsock/virtio: implement the datagram data path Sergio Lopez
2026-10-28 16:04 ` [PATCH RFC 4/8] vsock/virtio: negotiate datagram support on the guest Sergio Lopez
2026-10-28 16:04 ` [PATCH RFC 5/8] vhost/vsock: negotiate datagram support with guests Sergio Lopez
2026-10-28 16:04 ` [PATCH RFC 6/8] vsock: allow datagrams to use directional transports Sergio Lopez
2026-10-28 16:04 ` [PATCH RFC 7/8] vsock: preserve host-to-guest datagram boundaries Sergio Lopez
2026-10-28 16:04 ` [PATCH RFC 8/8] vsock: add datagram tests to vsock_test Sergio Lopez
2026-10-28 17:25 ` [PATCH RFC 0/8] vsock: add datagram support to virtio and vhost Alexei Starovoitov
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®