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 <[email protected]>
---
 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


Reply via email to