summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHerbert Xu <herbert@gondor.apana.org.au>2007-12-05 12:51:58 +0300
committerDavid S. Miller <davem@davemloft.net>2008-01-29 01:56:32 +0300
commit27ab2568649d5ba6c5a20212079b7c4f6da4ca0d (patch)
tree19bb85e73d7deb0adf40386af3117c2f397b653d
parentc8fecf2242a0ab7230210665986b8ef915e1ae9e (diff)
downloadlinux-27ab2568649d5ba6c5a20212079b7c4f6da4ca0d.tar.xz
[UDP]: Avoid repeated counting of checksum errors due to peeking
Currently it is possible for two processes to peek on the same socket and end up incrementing the error counter twice for the same packet. This patch fixes it by making skb_kill_datagram return whether it succeeded in unlinking the packet and only incrementing the counter if it did. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r--include/linux/skbuff.h2
-rw-r--r--net/core/datagram.c9
-rw-r--r--net/ipv4/udp.c5
-rw-r--r--net/ipv6/udp.c4
4 files changed, 13 insertions, 7 deletions
diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index d39f53ef66bb..17b3f70fbbc3 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -1549,7 +1549,7 @@ extern int skb_copy_and_csum_datagram_iovec(struct sk_buff *skb,
int hlen,
struct iovec *iov);
extern void skb_free_datagram(struct sock *sk, struct sk_buff *skb);
-extern void skb_kill_datagram(struct sock *sk, struct sk_buff *skb,
+extern int skb_kill_datagram(struct sock *sk, struct sk_buff *skb,
unsigned int flags);
extern __wsum skb_checksum(const struct sk_buff *skb, int offset,
int len, __wsum csum);
diff --git a/net/core/datagram.c b/net/core/datagram.c
index 029b93e246b4..fbd6c76436d0 100644
--- a/net/core/datagram.c
+++ b/net/core/datagram.c
@@ -217,20 +217,27 @@ void skb_free_datagram(struct sock *sk, struct sk_buff *skb)
* This function currently only disables BH when acquiring the
* sk_receive_queue lock. Therefore it must not be used in a
* context where that lock is acquired in an IRQ context.
+ *
+ * It returns 0 if the packet was removed by us.
*/
-void skb_kill_datagram(struct sock *sk, struct sk_buff *skb, unsigned int flags)
+int skb_kill_datagram(struct sock *sk, struct sk_buff *skb, unsigned int flags)
{
+ int err = 0;
+
if (flags & MSG_PEEK) {
+ err = -ENOENT;
spin_lock_bh(&sk->sk_receive_queue.lock);
if (skb == skb_peek(&sk->sk_receive_queue)) {
__skb_unlink(skb, &sk->sk_receive_queue);
atomic_dec(&skb->users);
+ err = 0;
}
spin_unlock_bh(&sk->sk_receive_queue.lock);
}
kfree_skb(skb);
+ return err;
}
EXPORT_SYMBOL(skb_kill_datagram);
diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index d0283b7fcec5..f50de5d5218d 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -899,9 +899,8 @@ out:
return err;
csum_copy_err:
- UDP_INC_STATS_USER(UDP_MIB_INERRORS, is_udplite);
-
- skb_kill_datagram(sk, skb, flags);
+ if (!skb_kill_datagram(sk, skb, flags))
+ UDP_INC_STATS_USER(UDP_MIB_INERRORS, is_udplite);
if (noblock)
return -EAGAIN;
diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c
index 77ab31b99232..87bccec9882a 100644
--- a/net/ipv6/udp.c
+++ b/net/ipv6/udp.c
@@ -207,8 +207,8 @@ out:
return err;
csum_copy_err:
- UDP6_INC_STATS_USER(UDP_MIB_INERRORS, is_udplite);
- skb_kill_datagram(sk, skb, flags);
+ if (!skb_kill_datagram(sk, skb, flags))
+ UDP6_INC_STATS_USER(UDP_MIB_INERRORS, is_udplite);
if (flags & MSG_DONTWAIT)
return -EAGAIN;