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)
Reasonably Related 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