mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: "Michael S. Tsirkin" <mst@redhat.com>
To: Oleg Drokin <green@linuxhacker.ru>
Cc: sgarzare@redhat.com, stefanha@redhat.com, jasowangio@gmail.com,
	eperezma@redhat.com, xuanzhuo@linux.alibaba.com,
	bobbyeshleman@meta.com, davem@davemloft.net, edumazet@kernel.org,
	kuba@kernel.org, pabeni@redhat.com, horms@kernel.org,
	netdev@vger.kernel.org, virtualization@lists.linux.dev,
	kvm@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH net 3/3] vsock/virtio: queue at most one credit update per socket
Date: Thu, 5 Nov 2026 19:19:55 -0500	[thread overview]
Message-ID: <20261105191917-mutt-send-email-mst@kernel.org> (raw)
In-Reply-To: <20261105000141.3617241-4-green@linuxhacker.ru>

On Wed, Nov 04, 2026 at 07:01:41PM -0500, Oleg Drokin wrote:
> A credit update carries no data, so the send credit never limits it,
> and the peer can solicit one at will: with OP_CREDIT_REQUEST, or by
> sending data that a local reader drains (a read that empties the
> receive queue sends one). A peer that posts no rx buffers can therefore
> make the other side queue credit updates without limit, about 1 KiB of
> unaccounted slab each for a 44-byte packet.
> 
> Do not queue a credit update while the socket's last queued packet is
> already one. Every packet carries fwd_cnt and buf_alloc, so a packet
> queued later makes the queued update redundant and lets a new one be
> queued after it. When a transport puts a queued update on the wire
> (vhost once it has an rx buffer for it, the guest driver when it adds
> it to the tx virtqueue, loopback when it delivers it) and nothing has
> been queued after it, it refreshes the update's fwd_cnt and buf_alloc
> and releases the socket. So a suppressed update always reaches the
> peer, either in the queued one or in a later packet, and the fwd_cnt
> the peer sees never goes back.
> 
> connect() can move a socket to another transport, freeing its
> virtio_vsock_sock, while packets are still queued in the old one. So a
> tracked update holds a reference on the virtio_vsock_sock, and its skb
> destructor clears the socket's pointer to it and drops the reference,
> however the skb is freed: sent, purged or dropped.
> 
> A peer can thus make a connection queue at most one credit update, plus
> one per packet the socket itself queued after it.
> 
> Counting credit updates in queued_replies instead would not work: most
> of them are sent by local reads, not in answer to the peer, and under
> bidirectional traffic vhost would stop reading the guest's tx queue
> while the guest stops reading its rx queue, each waiting for the other.
> 
> Fixes: 06a8fc78367d ("VSOCK: Introduce virtio_vsock_common.ko")
> Signed-off-by: Oleg Drokin <green@linuxhacker.ru>
> ---
>  drivers/vhost/vsock.c                   |  2 +
>  include/linux/virtio_vsock.h            |  5 ++
>  net/vmw_vsock/virtio_transport.c        |  2 +
>  net/vmw_vsock/virtio_transport_common.c | 62 ++++++++++++++++++++++++-
>  net/vmw_vsock/vsock_loopback.c          |  1 +
>  5 files changed, 71 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c
> index b0004cb48999..f1b47a826ca4 100644
> --- a/drivers/vhost/vsock.c
> +++ b/drivers/vhost/vsock.c
> @@ -176,6 +176,8 @@ vhost_transport_do_send_pkt(struct vhost_vsock *vsock,
>  			break;
>  		}
>  
> +		virtio_transport_credit_update_xmit(skb);
> +
>  		if (out) {
>  			restart_tx |= vhost_vsock_drop_skb(vsock, skb);
>  			vq_err(vq, "Expected 0 output buffers, got %u\n", out);
> diff --git a/include/linux/virtio_vsock.h b/include/linux/virtio_vsock.h
> index f91704731057..6c3fc8faca0b 100644
> --- a/include/linux/virtio_vsock.h
> +++ b/include/linux/virtio_vsock.h
> @@ -13,6 +13,7 @@ struct virtio_vsock_skb_cb {
>  	bool reply;
>  	bool tap_delivered;
>  	u32 offset;
> +	struct virtio_vsock_sock *vvs;
>  };
>  
>  #define VIRTIO_VSOCK_SKB_CB(skb) ((struct virtio_vsock_skb_cb *)((skb)->cb))
> @@ -167,6 +168,9 @@ struct virtio_vsock_sock {
>  	u32 buf_used;
>  	struct sk_buff_head rx_queue;
>  	u32 msg_count;
> +	struct sk_buff *credit_update;
> +
> +	refcount_t refcnt;

why all this complexity? can you not just peek at the queue tail
instead?


>  };
>  
>  struct virtio_vsock_pkt_info {
> @@ -283,6 +287,7 @@ void virtio_transport_destruct(struct vsock_sock *vsk);
>  void virtio_transport_recv_pkt(struct virtio_transport *t,
>  			       struct sk_buff *skb, struct net *net);
>  void virtio_transport_inc_tx_pkt(struct virtio_vsock_sock *vvs, struct sk_buff *skb);
> +void virtio_transport_credit_update_xmit(struct sk_buff *skb);
>  u32 virtio_transport_get_credit(struct virtio_vsock_sock *vvs, u32 wanted);
>  void virtio_transport_put_credit(struct virtio_vsock_sock *vvs, u32 credit);
>  void virtio_transport_deliver_tap_pkt(struct sk_buff *skb);
> diff --git a/net/vmw_vsock/virtio_transport.c b/net/vmw_vsock/virtio_transport.c
> index 4f9aa9c4c3aa..82cd52249de3 100644
> --- a/net/vmw_vsock/virtio_transport.c
> +++ b/net/vmw_vsock/virtio_transport.c
> @@ -104,6 +104,8 @@ static int virtio_transport_send_skb(struct sk_buff *skb, struct virtqueue *vq,
>  	int ret, in_sg = 0, out_sg = 0;
>  	struct scatterlist **sgs;
>  
> +	virtio_transport_credit_update_xmit(skb);
> +
>  	sgs = vsock->out_sgs;
>  	sg_init_one(sgs[out_sg], virtio_vsock_hdr(skb),
>  		    sizeof(*virtio_vsock_hdr(skb)));
> diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
> index 74a8fdf825d5..65d6e0790e72 100644
> --- a/net/vmw_vsock/virtio_transport_common.c
> +++ b/net/vmw_vsock/virtio_transport_common.c
> @@ -604,18 +604,68 @@ static void virtio_transport_dec_rx_pkt(struct virtio_vsock_sock *vvs,
>  	vvs->fwd_cnt += bytes_dequeued;
>  }
>  
> +static void virtio_transport_put_vvs(struct virtio_vsock_sock *vvs)
> +{
> +	if (refcount_dec_and_test(&vvs->refcnt))
> +		kfree(vvs);
> +}
> +
> +static void virtio_transport_credit_update_destruct(struct sk_buff *skb)
> +{
> +	struct virtio_vsock_sock *vvs = VIRTIO_VSOCK_SKB_CB(skb)->vvs;
> +
> +	spin_lock_bh(&vvs->rx_lock);
> +	if (vvs->credit_update == skb)
> +		vvs->credit_update = NULL;
> +	spin_unlock_bh(&vvs->rx_lock);
> +
> +	virtio_transport_put_vvs(vvs);
> +	sock_wfree(skb);
> +}
> +
>  void virtio_transport_inc_tx_pkt(struct virtio_vsock_sock *vvs, struct sk_buff *skb)
>  {
>  	struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
> +	bool track = le16_to_cpu(hdr->op) == VIRTIO_VSOCK_OP_CREDIT_UPDATE &&
> +		     skb->destructor == sock_wfree;
> +
> +	/* The skb pins vvs, which connect() may detach from the socket. */
> +	if (track) {
> +		refcount_inc(&vvs->refcnt);
> +		VIRTIO_VSOCK_SKB_CB(skb)->vvs = vvs;
> +		skb->destructor = virtio_transport_credit_update_destruct;
> +	}
>  
>  	spin_lock_bh(&vvs->rx_lock);
>  	vvs->last_fwd_cnt = vvs->fwd_cnt;
>  	hdr->fwd_cnt = cpu_to_le32(vvs->fwd_cnt);
>  	hdr->buf_alloc = cpu_to_le32(vvs->buf_alloc);
> +	vvs->credit_update = track ? skb : NULL;
>  	spin_unlock_bh(&vvs->rx_lock);
>  }
>  EXPORT_SYMBOL_GPL(virtio_transport_inc_tx_pkt);
>  
> +/* Called by the transports when @skb is put on the wire. */
> +void virtio_transport_credit_update_xmit(struct sk_buff *skb)
> +{
> +	struct virtio_vsock_sock *vvs = VIRTIO_VSOCK_SKB_CB(skb)->vvs;
> +	struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
> +
> +	if (!vvs)
> +		return;
> +
> +	spin_lock_bh(&vvs->rx_lock);
> +	/* Still the socket's last packet: carry what was suppressed. */
> +	if (vvs->credit_update == skb) {
> +		vvs->credit_update = NULL;
> +		vvs->last_fwd_cnt = vvs->fwd_cnt;
> +		hdr->fwd_cnt = cpu_to_le32(vvs->fwd_cnt);
> +		hdr->buf_alloc = cpu_to_le32(vvs->buf_alloc);
> +	}
> +	spin_unlock_bh(&vvs->rx_lock);
> +}
> +EXPORT_SYMBOL_GPL(virtio_transport_credit_update_xmit);
> +
>  void virtio_transport_consume_skb_sent(struct sk_buff *skb, bool consume)
>  {
>  	struct sock *s = skb->sk;
> @@ -667,11 +717,20 @@ EXPORT_SYMBOL_GPL(virtio_transport_put_credit);
>  
>  static int virtio_transport_send_credit_update(struct vsock_sock *vsk)
>  {
> +	struct virtio_vsock_sock *vvs = vsk->trans;
>  	struct virtio_vsock_pkt_info info = {
>  		.op = VIRTIO_VSOCK_OP_CREDIT_UPDATE,
>  		.vsk = vsk,
>  		.net = sock_net(sk_vsock(vsk)),
>  	};
> +	bool queued;
> +
> +	spin_lock_bh(&vvs->rx_lock);
> +	queued = vvs->credit_update;
> +	spin_unlock_bh(&vvs->rx_lock);
> +
> +	if (queued)
> +		return 0;
>  
>  	return virtio_transport_send_pkt_info(vsk, &info);
>  }
> @@ -1086,6 +1145,7 @@ int virtio_transport_do_socket_init(struct vsock_sock *vsk,
>  	spin_lock_init(&vvs->rx_lock);
>  	spin_lock_init(&vvs->tx_lock);
>  	skb_queue_head_init(&vvs->rx_queue);
> +	refcount_set(&vvs->refcnt, 1);
>  
>  	return 0;
>  }
> @@ -1276,8 +1336,8 @@ void virtio_transport_destruct(struct vsock_sock *vsk)
>  
>  	virtio_transport_cancel_close_work(vsk, true);
>  
> -	kfree(vvs);
>  	vsk->trans = NULL;
> +	virtio_transport_put_vvs(vvs);
>  }
>  EXPORT_SYMBOL_GPL(virtio_transport_destruct);
>  
> diff --git a/net/vmw_vsock/vsock_loopback.c b/net/vmw_vsock/vsock_loopback.c
> index 8068d1b6e851..84342a97a513 100644
> --- a/net/vmw_vsock/vsock_loopback.c
> +++ b/net/vmw_vsock/vsock_loopback.c
> @@ -134,6 +134,7 @@ static void vsock_loopback_work(struct work_struct *work)
>  	spin_unlock_bh(&vsock->pkt_queue.lock);
>  
>  	while ((skb = __skb_dequeue(&pkts))) {
> +		virtio_transport_credit_update_xmit(skb);
>  		/* Decrement the bytes_unsent counter without deallocating skb
>  		 * It is freed by the receiver.
>  		 */
> -- 
> 2.56.0
> 


  reply	other threads:[~2026-11-06  0:20 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-05  0:01 [PATCH net 0/3] vsock/virtio: bound what a peer can make the host queue Oleg Drokin
2026-11-05  0:01 ` [PATCH net 1/3] vhost/vsock: release the accounting of packets dropped on rx errors Oleg Drokin
2026-11-05  0:01 ` [PATCH net 2/3] vsock/virtio: bound the send credit by bytes not yet consumed by the transport Oleg Drokin
2026-11-05  0:01 ` [PATCH net 3/3] vsock/virtio: queue at most one credit update per socket Oleg Drokin
2026-11-06  0:19   ` Michael S. Tsirkin [this message]
2026-11-06  5:02     ` Oleg Drokin
2026-11-05  0:03 ` [PATCH net 0/3] vsock/virtio: bound what a peer can make the host queue netdev-bot+sinfo
2026-11-05  0:19   ` Oleg Drokin

Reply instructions:

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

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

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

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

  git send-email \
    --in-reply-to=20261105191917-mutt-send-email-mst@kernel.org \
    --to=mst@redhat.com \
    --cc=bobbyeshleman@meta.com \
    --cc=davem@davemloft.net \
    --cc=edumazet@kernel.org \
    --cc=eperezma@redhat.com \
    --cc=green@linuxhacker.ru \
    --cc=horms@kernel.org \
    --cc=jasowangio@gmail.com \
    --cc=kuba@kernel.org \
    --cc=kvm@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=sgarzare@redhat.com \
    --cc=stefanha@redhat.com \
    --cc=virtualization@lists.linux.dev \
    --cc=xuanzhuo@linux.alibaba.com \
    /path/to/YOUR_REPLY

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

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

all inboxes | Powered by JetHome®