aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHerbert Xu <herbert@gondor.apana.org.au>2005-12-14 02:16:37 -0500
committerDavid S. Miller <davem@sunset.davemloft.net>2006-01-03 16:10:41 -0500
commit3305b80c214c642b89cd5c21af83bc91ec13f8bd (patch)
tree909ed75c500d0ac422738781f84a819c933703c5
parent57cca05af1e20fdc65b55be52c042c234f86c866 (diff)
[IP]: Simplify and consolidate MSG_PEEK error handling
When a packet is obtained from skb_recv_datagram with MSG_PEEK enabled it is left on the socket receive queue. This means that when we detect a checksum error we have to be careful when trying to free the packet as someone could have dequeued it in the time being. Currently this delicate logic is duplicated three times between UDPv4, UDPv6 and RAWv6. This patch moves them into a one place and simplifies the code somewhat. This is based on a suggestion by Eric Dumazet. 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.c36
-rw-r--r--net/ipv4/udp.c15
-rw-r--r--net/ipv6/raw.c16
-rw-r--r--net/ipv6/udp.c16
5 files changed, 44 insertions, 41 deletions
diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index 8c5d6001a923..97f6580ce039 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -1239,6 +1239,8 @@ extern int skb_copy_and_csum_datagram_iovec(struct sk_buff *skb,
1239 int hlen, 1239 int hlen,
1240 struct iovec *iov); 1240 struct iovec *iov);
1241extern void skb_free_datagram(struct sock *sk, struct sk_buff *skb); 1241extern void skb_free_datagram(struct sock *sk, struct sk_buff *skb);
1242extern void skb_kill_datagram(struct sock *sk, struct sk_buff *skb,
1243 unsigned int flags);
1242extern unsigned int skb_checksum(const struct sk_buff *skb, int offset, 1244extern unsigned int skb_checksum(const struct sk_buff *skb, int offset,
1243 int len, unsigned int csum); 1245 int len, unsigned int csum);
1244extern int skb_copy_bits(const struct sk_buff *skb, int offset, 1246extern int skb_copy_bits(const struct sk_buff *skb, int offset,
diff --git a/net/core/datagram.c b/net/core/datagram.c
index 1bcfef51ac58..f8d322e1ea92 100644
--- a/net/core/datagram.c
+++ b/net/core/datagram.c
@@ -47,6 +47,7 @@
47#include <linux/rtnetlink.h> 47#include <linux/rtnetlink.h>
48#include <linux/poll.h> 48#include <linux/poll.h>
49#include <linux/highmem.h> 49#include <linux/highmem.h>
50#include <linux/spinlock.h>
50 51
51#include <net/protocol.h> 52#include <net/protocol.h>
52#include <linux/skbuff.h> 53#include <linux/skbuff.h>
@@ -200,6 +201,41 @@ void skb_free_datagram(struct sock *sk, struct sk_buff *skb)
200} 201}
201 202
202/** 203/**
204 * skb_kill_datagram - Free a datagram skbuff forcibly
205 * @sk: socket
206 * @skb: datagram skbuff
207 * @flags: MSG_ flags
208 *
209 * This function frees a datagram skbuff that was received by
210 * skb_recv_datagram. The flags argument must match the one
211 * used for skb_recv_datagram.
212 *
213 * If the MSG_PEEK flag is set, and the packet is still on the
214 * receive queue of the socket, it will be taken off the queue
215 * before it is freed.
216 *
217 * This function currently only disables BH when acquiring the
218 * sk_receive_queue lock. Therefore it must not be used in a
219 * context where that lock is acquired in an IRQ context.
220 */
221
222void skb_kill_datagram(struct sock *sk, struct sk_buff *skb, unsigned int flags)
223{
224 if (flags & MSG_PEEK) {
225 spin_lock_bh(&sk->sk_receive_queue.lock);
226 if (skb == skb_peek(&sk->sk_receive_queue)) {
227 __skb_unlink(skb, &sk->sk_receive_queue);
228 atomic_dec(&skb->users);
229 }
230 spin_unlock_bh(&sk->sk_receive_queue.lock);
231 }
232
233 kfree_skb(skb);
234}
235
236EXPORT_SYMBOL(skb_kill_datagram);
237
238/**
203 * skb_copy_datagram_iovec - Copy a datagram to an iovec. 239 * skb_copy_datagram_iovec - Copy a datagram to an iovec.
204 * @skb: buffer to copy 240 * @skb: buffer to copy
205 * @offset: offset in the buffer to start copying from 241 * @offset: offset in the buffer to start copying from
diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index 2422a5f7195d..012c4621e40a 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -846,20 +846,7 @@ out:
846csum_copy_err: 846csum_copy_err:
847 UDP_INC_STATS_BH(UDP_MIB_INERRORS); 847 UDP_INC_STATS_BH(UDP_MIB_INERRORS);
848 848
849 /* Clear queue. */ 849 skb_kill_datagram(sk, skb, flags);
850 if (flags&MSG_PEEK) {
851 int clear = 0;
852 spin_lock_bh(&sk->sk_receive_queue.lock);
853 if (skb == skb_peek(&sk->sk_receive_queue)) {
854 __skb_unlink(skb, &sk->sk_receive_queue);
855 clear = 1;
856 }
857 spin_unlock_bh(&sk->sk_receive_queue.lock);
858 if (clear)
859 kfree_skb(skb);
860 }
861
862 skb_free_datagram(sk, skb);
863 850
864 if (noblock) 851 if (noblock)
865 return -EAGAIN; 852 return -EAGAIN;
diff --git a/net/ipv6/raw.c b/net/ipv6/raw.c
index a66900cda2af..66f1d12ea578 100644
--- a/net/ipv6/raw.c
+++ b/net/ipv6/raw.c
@@ -32,6 +32,7 @@
32#include <linux/icmpv6.h> 32#include <linux/icmpv6.h>
33#include <linux/netfilter.h> 33#include <linux/netfilter.h>
34#include <linux/netfilter_ipv6.h> 34#include <linux/netfilter_ipv6.h>
35#include <linux/skbuff.h>
35#include <asm/uaccess.h> 36#include <asm/uaccess.h>
36#include <asm/ioctls.h> 37#include <asm/ioctls.h>
37#include <asm/bug.h> 38#include <asm/bug.h>
@@ -433,25 +434,14 @@ out:
433 return err; 434 return err;
434 435
435csum_copy_err: 436csum_copy_err:
436 /* Clear queue. */ 437 skb_kill_datagram(sk, skb, flags);
437 if (flags&MSG_PEEK) {
438 int clear = 0;
439 spin_lock_bh(&sk->sk_receive_queue.lock);
440 if (skb == skb_peek(&sk->sk_receive_queue)) {
441 __skb_unlink(skb, &sk->sk_receive_queue);
442 clear = 1;
443 }
444 spin_unlock_bh(&sk->sk_receive_queue.lock);
445 if (clear)
446 kfree_skb(skb);
447 }
448 438
449 /* Error for blocking case is chosen to masquerade 439 /* Error for blocking case is chosen to masquerade
450 as some normal condition. 440 as some normal condition.
451 */ 441 */
452 err = (flags&MSG_DONTWAIT) ? -EAGAIN : -EHOSTUNREACH; 442 err = (flags&MSG_DONTWAIT) ? -EAGAIN : -EHOSTUNREACH;
453 /* FIXME: increment a raw6 drops counter here */ 443 /* FIXME: increment a raw6 drops counter here */
454 goto out_free; 444 goto out;
455} 445}
456 446
457static int rawv6_push_pending_frames(struct sock *sk, struct flowi *fl, 447static int rawv6_push_pending_frames(struct sock *sk, struct flowi *fl,
diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c
index 5cc8731eb55b..d8538dcea813 100644
--- a/net/ipv6/udp.c
+++ b/net/ipv6/udp.c
@@ -36,6 +36,7 @@
36#include <linux/ipv6.h> 36#include <linux/ipv6.h>
37#include <linux/icmpv6.h> 37#include <linux/icmpv6.h>
38#include <linux/init.h> 38#include <linux/init.h>
39#include <linux/skbuff.h>
39#include <asm/uaccess.h> 40#include <asm/uaccess.h>
40 41
41#include <net/sock.h> 42#include <net/sock.h>
@@ -300,20 +301,7 @@ out:
300 return err; 301 return err;
301 302
302csum_copy_err: 303csum_copy_err:
303 /* Clear queue. */ 304 skb_kill_datagram(sk, skb, flags);
304 if (flags&MSG_PEEK) {
305 int clear = 0;
306 spin_lock_bh(&sk->sk_receive_queue.lock);
307 if (skb == skb_peek(&sk->sk_receive_queue)) {
308 __skb_unlink(skb, &sk->sk_receive_queue);
309 clear = 1;
310 }
311 spin_unlock_bh(&sk->sk_receive_queue.lock);
312 if (clear)
313 kfree_skb(skb);
314 }
315
316 skb_free_datagram(sk, skb);
317 305
318 if (flags & MSG_DONTWAIT) { 306 if (flags & MSG_DONTWAIT) {
319 UDP6_INC_STATS_USER(UDP_MIB_INERRORS); 307 UDP6_INC_STATS_USER(UDP_MIB_INERRORS);