Rusty Russell
2008-May-26 07:42 UTC
[PATCH 1/3] virtio: fix virtio_net xmit of freed skb bug
If we fail to transmit a packet, we assume the queue is full and put the skb into last_xmit_skb. However, if more space frees up before we xmit it, we loop, and the result can be transmitting the same skb twice. Fix is simple: set skb to NULL if we've used it in some way, and check before sending. Signed-off-by: Rusty Russell <rusty at rustcorp.com.au> --- drivers/net/virtio_net.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff -r 564237b31993 drivers/net/virtio_net.c --- a/drivers/net/virtio_net.c Mon May 19 12:22:00 2008 +1000 +++ b/drivers/net/virtio_net.c Mon May 19 12:24:58 2008 +1000 @@ -287,21 +287,25 @@ again: free_old_xmit_skbs(vi); /* If we has a buffer left over from last time, send it now. */ - if (vi->last_xmit_skb) { + if (unlikely(vi->last_xmit_skb)) { if (xmit_skb(vi, vi->last_xmit_skb) != 0) { /* Drop this skb: we only queue one. */ vi->dev->stats.tx_dropped++; kfree_skb(skb); + skb = NULL; goto stop_queue; } vi->last_xmit_skb = NULL; } /* Put new one in send queue and do transmit */ - __skb_queue_head(&vi->send, skb); - if (xmit_skb(vi, skb) != 0) { - vi->last_xmit_skb = skb; - goto stop_queue; + if (likely(skb)) { + __skb_queue_head(&vi->send, skb); + if (xmit_skb(vi, skb) != 0) { + vi->last_xmit_skb = skb; + skb = NULL; + goto stop_queue; + } } done: vi->svq->vq_ops->kick(vi->svq);
Rusty Russell
2008-May-26 07:48 UTC
[PATCH 2/3] virtio: fix delayed xmit of packet and freeing of old packets.
Because we cache the last failed-to-xmit packet, if there are no packets queued behind that one we may never send it (reproduced here as TCP stalls, "cured" by an outgoing ping). Cc: Mark McLoughlin <markmc at redhat.com> Signed-off-by: Rusty Russell <rusty at rustcorp.com.au> --- drivers/net/virtio_net.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff -r 1d1ff03de434 drivers/net/virtio_net.c --- a/drivers/net/virtio_net.c Mon May 26 11:03:26 2008 +1000 +++ b/drivers/net/virtio_net.c Mon May 26 16:37:20 2008 +1000 @@ -47,6 +47,9 @@ struct virtnet_info /* Number of input buffers, and max we've ever had. */ unsigned int num, max; + /* For cleaning up after transmission. */ + struct tasklet_struct tasklet; + /* Receive & send queues. */ struct sk_buff_head recv; struct sk_buff_head send; @@ -68,8 +71,13 @@ static void skb_xmit_done(struct virtque /* Suppress further interrupts. */ svq->vq_ops->disable_cb(svq); + /* We were waiting for more output buffers. */ netif_wake_queue(vi->dev); + + /* Make sure we re-xmit last_xmit_skb: if there are no more packets + * queued, start_xmit won't be called. */ + tasklet_schedule(&vi->tasklet); } static void receive_skb(struct net_device *dev, struct sk_buff *skb, @@ -278,6 +286,18 @@ static int xmit_skb(struct virtnet_info return vi->svq->vq_ops->add_buf(vi->svq, sg, num, 0, skb); } +static void xmit_tasklet(unsigned long data) +{ + struct virtnet_info *vi = (void *)data; + + netif_tx_lock_bh(vi->dev); + if (vi->last_xmit_skb && xmit_skb(vi, vi->last_xmit_skb) == 0) { + vi->svq->vq_ops->kick(vi->svq); + vi->last_xmit_skb = NULL; + } + netif_tx_unlock_bh(vi->dev); +} + static int start_xmit(struct sk_buff *skb, struct net_device *dev) { struct virtnet_info *vi = netdev_priv(dev); @@ -432,6 +452,8 @@ static int virtnet_probe(struct virtio_d skb_queue_head_init(&vi->recv); skb_queue_head_init(&vi->send); + tasklet_init(&vi->tasklet, xmit_tasklet, (unsigned long)vi); + err = register_netdev(dev); if (err) { pr_debug("virtio_net: registering device failed\n");
From: Wang Chen <wangchen at cn.fujitsu.com> Use standard routine for queue purging. Signed-off-by: Wang Chen <wangchen at cn.fujitsu.com> Signed-off-by: Rusty Russell <rusty at rustcorp.com.au> --- drivers/net/virtio_net.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c index f926b5a..fe7cdf2 100644 --- a/drivers/net/virtio_net.c +++ b/drivers/net/virtio_net.c @@ -470,8 +470,7 @@ static void virtnet_remove(struct virtio_device *vdev) kfree_skb(skb); vi->num--; } - while ((skb = __skb_dequeue(&vi->send)) != NULL) - kfree_skb(skb); + __skb_queue_purge(&vi->send); BUG_ON(vi->num != 0);
Rusty Russell said the following on 2008-5-26 15:53:> From: Wang Chen <wangchen at cn.fujitsu.com> > > Use standard routine for queue purging. > > Signed-off-by: Wang Chen <wangchen at cn.fujitsu.com> > Signed-off-by: Rusty Russell <rusty at rustcorp.com.au>Rusty, Jeff has applied this one and David has merged it and send a pull request to Linus. Thanks.
Mark McLoughlin
2008-May-27 11:01 UTC
[PATCH 2/3] virtio: fix delayed xmit of packet and freeing of old packets.
On Mon, 2008-05-26 at 17:48 +1000, Rusty Russell wrote:> Because we cache the last failed-to-xmit packet, if there are no > packets queued behind that one we may never send it (reproduced here > as TCP stalls, "cured" by an outgoing ping)....> diff -r 1d1ff03de434 drivers/net/virtio_net.c > --- a/drivers/net/virtio_net.c Mon May 26 11:03:26 2008 +1000 > +++ b/drivers/net/virtio_net.c Mon May 26 16:37:20 2008 +1000...> @@ -432,6 +452,8 @@ static int virtnet_probe(struct virtio_d > skb_queue_head_init(&vi->recv); > skb_queue_head_init(&vi->send); > > + tasklet_init(&vi->tasklet, xmit_tasklet, (unsigned long)vi); > + > err = register_netdev(dev);Missing a tasklet_kill() in virtnet_remove() ? Cheers, Mark.
Mark McLoughlin
2008-May-27 11:06 UTC
[PATCH 1/3] virtio: fix virtio_net xmit of freed skb bug
On Mon, 2008-05-26 at 17:42 +1000, Rusty Russell wrote:> If we fail to transmit a packet, we assume the queue is full and put > the skb into last_xmit_skb. However, if more space frees up before we > xmit it, we loop, and the result can be transmitting the same skb twice. > > Fix is simple: set skb to NULL if we've used it in some way, and check > before sending....> diff -r 564237b31993 drivers/net/virtio_net.c > --- a/drivers/net/virtio_net.c Mon May 19 12:22:00 2008 +1000 > +++ b/drivers/net/virtio_net.c Mon May 19 12:24:58 2008 +1000 > @@ -287,21 +287,25 @@ again: > free_old_xmit_skbs(vi); > > /* If we has a buffer left over from last time, send it now. */ > - if (vi->last_xmit_skb) { > + if (unlikely(vi->last_xmit_skb)) { > if (xmit_skb(vi, vi->last_xmit_skb) != 0) { > /* Drop this skb: we only queue one. */ > vi->dev->stats.tx_dropped++; > kfree_skb(skb); > + skb = NULL; > goto stop_queue; > } > vi->last_xmit_skb = NULL;With this, may drop an skb and then later in the function discover that we could have sent it after all. Poor wee skb :) How about the incremental patch below? Cheers, Mark. Subject: [PATCH] virtio_net: Delay dropping tx skbs Currently we drop the skb in start_xmit() if we have a queued buffer and fail to transmit it. However, if we delay dropping it until we've stopped the queue and enabled the tx notification callback, then there is a chance space might become available for it. Signed-off-by: Mark McLoughlin <markmc at redhat.com> --- drivers/net/virtio_net.c | 20 ++++++++++---------- 1 files changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c index d50f4fe..30af05c 100644 --- a/drivers/net/virtio_net.c +++ b/drivers/net/virtio_net.c @@ -287,16 +287,11 @@ again: free_old_xmit_skbs(vi); /* If we has a buffer left over from last time, send it now. */ - if (unlikely(vi->last_xmit_skb)) { - if (xmit_skb(vi, vi->last_xmit_skb) != 0) { - /* Drop this skb: we only queue one. */ - vi->dev->stats.tx_dropped++; - kfree_skb(skb); - skb = NULL; - goto stop_queue; - } - vi->last_xmit_skb = NULL; - } + if (unlikely(vi->last_xmit_skb) && + xmit_skb(vi, vi->last_xmit_skb) != 0) + goto stop_queue; + + vi->last_xmit_skb = NULL; /* Put new one in send queue and do transmit */ if (likely(skb)) { @@ -322,6 +317,11 @@ stop_queue: netif_start_queue(dev); goto again; } + if (skb) { + /* Drop this skb: we only queue one. */ + vi->dev->stats.tx_dropped++; + kfree_skb(skb); + } goto done; }
Rusty Russell
2008-May-28 04:59 UTC
[PATCH 2/3] virtio: fix delayed xmit of packet and freeing of old packets.
On Tuesday 27 May 2008 21:01:08 Mark McLoughlin wrote:> On Mon, 2008-05-26 at 17:48 +1000, Rusty Russell wrote: > > diff -r 1d1ff03de434 drivers/net/virtio_net.c > ... > > + tasklet_init(&vi->tasklet, xmit_tasklet, (unsigned long)vi); > > + > > err = register_netdev(dev); > > Missing a tasklet_kill() in virtnet_remove() ?Thanks Mark! Added. Nice spotting, Rusty.
Jeff Garzik
2008-May-31 02:12 UTC
[PATCH 1/3] virtio: fix virtio_net xmit of freed skb bug
Rusty Russell wrote:> If we fail to transmit a packet, we assume the queue is full and put > the skb into last_xmit_skb. However, if more space frees up before we > xmit it, we loop, and the result can be transmitting the same skb twice. > > Fix is simple: set skb to NULL if we've used it in some way, and check > before sending. > > Signed-off-by: Rusty Russell <rusty at rustcorp.com.au> > --- > drivers/net/virtio_net.c | 14 +++++++++----- > 1 file changed, 9 insertions(+), 5 deletions(-)applied 1-2 (#3 was already applied)
Apparently Analagous Threads
- [PATCH 1/3] virtio: fix virtio_net xmit of freed skb bug
- [PATCH 2/4] virtio_net: return NETDEV_TX_BUSY instead of queueing an extra skb.
- [PATCH 2/4] virtio_net: return NETDEV_TX_BUSY instead of queueing an extra skb.
- [PATCH 1/4] virtio_net: Fix skb->csum_start computation
- [PATCH 2/5] virtio: fix virtio_net xmit of freed skb bug