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 --- 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