Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3305b80c authored by Herbert Xu's avatar Herbert Xu Committed by David S. Miller
Browse files

[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: default avatarHerbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 57cca05a
Loading
Loading
Loading
Loading
+2 −0
Original line number Original line Diff line number Diff line
@@ -1239,6 +1239,8 @@ extern int skb_copy_and_csum_datagram_iovec(struct sk_buff *skb,
							int hlen,
							int hlen,
							struct iovec *iov);
							struct iovec *iov);
extern void	       skb_free_datagram(struct sock *sk, struct sk_buff *skb);
extern void	       skb_free_datagram(struct sock *sk, struct sk_buff *skb);
extern void	       skb_kill_datagram(struct sock *sk, struct sk_buff *skb,
					 unsigned int flags);
extern unsigned int    skb_checksum(const struct sk_buff *skb, int offset,
extern unsigned int    skb_checksum(const struct sk_buff *skb, int offset,
				    int len, unsigned int csum);
				    int len, unsigned int csum);
extern int	       skb_copy_bits(const struct sk_buff *skb, int offset,
extern int	       skb_copy_bits(const struct sk_buff *skb, int offset,
+36 −0
Original line number Original line Diff line number Diff line
@@ -47,6 +47,7 @@
#include <linux/rtnetlink.h>
#include <linux/rtnetlink.h>
#include <linux/poll.h>
#include <linux/poll.h>
#include <linux/highmem.h>
#include <linux/highmem.h>
#include <linux/spinlock.h>


#include <net/protocol.h>
#include <net/protocol.h>
#include <linux/skbuff.h>
#include <linux/skbuff.h>
@@ -199,6 +200,41 @@ void skb_free_datagram(struct sock *sk, struct sk_buff *skb)
	kfree_skb(skb);
	kfree_skb(skb);
}
}


/**
 *	skb_kill_datagram - Free a datagram skbuff forcibly
 *	@sk: socket
 *	@skb: datagram skbuff
 *	@flags: MSG_ flags
 *
 *	This function frees a datagram skbuff that was received by
 *	skb_recv_datagram.  The flags argument must match the one
 *	used for skb_recv_datagram.
 *
 *	If the MSG_PEEK flag is set, and the packet is still on the
 *	receive queue of the socket, it will be taken off the queue
 *	before it is freed.
 *
 *	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.
 */

void skb_kill_datagram(struct sock *sk, struct sk_buff *skb, unsigned int flags)
{
	if (flags & MSG_PEEK) {
		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);
		}
		spin_unlock_bh(&sk->sk_receive_queue.lock);
	}

	kfree_skb(skb);
}

EXPORT_SYMBOL(skb_kill_datagram);

/**
/**
 *	skb_copy_datagram_iovec - Copy a datagram to an iovec.
 *	skb_copy_datagram_iovec - Copy a datagram to an iovec.
 *	@skb: buffer to copy
 *	@skb: buffer to copy
+1 −14
Original line number Original line Diff line number Diff line
@@ -846,20 +846,7 @@ out:
csum_copy_err:
csum_copy_err:
	UDP_INC_STATS_BH(UDP_MIB_INERRORS);
	UDP_INC_STATS_BH(UDP_MIB_INERRORS);


	/* Clear queue. */
	skb_kill_datagram(sk, skb, flags);
	if (flags&MSG_PEEK) {
		int clear = 0;
		spin_lock_bh(&sk->sk_receive_queue.lock);
		if (skb == skb_peek(&sk->sk_receive_queue)) {
			__skb_unlink(skb, &sk->sk_receive_queue);
			clear = 1;
		}
		spin_unlock_bh(&sk->sk_receive_queue.lock);
		if (clear)
			kfree_skb(skb);
	}

	skb_free_datagram(sk, skb);


	if (noblock)
	if (noblock)
		return -EAGAIN;	
		return -EAGAIN;	
+3 −13
Original line number Original line Diff line number Diff line
@@ -32,6 +32,7 @@
#include <linux/icmpv6.h>
#include <linux/icmpv6.h>
#include <linux/netfilter.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv6.h>
#include <linux/netfilter_ipv6.h>
#include <linux/skbuff.h>
#include <asm/uaccess.h>
#include <asm/uaccess.h>
#include <asm/ioctls.h>
#include <asm/ioctls.h>
#include <asm/bug.h>
#include <asm/bug.h>
@@ -433,25 +434,14 @@ out:
	return err;
	return err;


csum_copy_err:
csum_copy_err:
	/* Clear queue. */
	skb_kill_datagram(sk, skb, flags);
	if (flags&MSG_PEEK) {
		int clear = 0;
		spin_lock_bh(&sk->sk_receive_queue.lock);
		if (skb == skb_peek(&sk->sk_receive_queue)) {
			__skb_unlink(skb, &sk->sk_receive_queue);
			clear = 1;
		}
		spin_unlock_bh(&sk->sk_receive_queue.lock);
		if (clear)
			kfree_skb(skb);
	}


	/* Error for blocking case is chosen to masquerade
	/* Error for blocking case is chosen to masquerade
	   as some normal condition.
	   as some normal condition.
	 */
	 */
	err = (flags&MSG_DONTWAIT) ? -EAGAIN : -EHOSTUNREACH;
	err = (flags&MSG_DONTWAIT) ? -EAGAIN : -EHOSTUNREACH;
	/* FIXME: increment a raw6 drops counter here */
	/* FIXME: increment a raw6 drops counter here */
	goto out_free;
	goto out;
}
}


static int rawv6_push_pending_frames(struct sock *sk, struct flowi *fl,
static int rawv6_push_pending_frames(struct sock *sk, struct flowi *fl,
+2 −14
Original line number Original line Diff line number Diff line
@@ -36,6 +36,7 @@
#include <linux/ipv6.h>
#include <linux/ipv6.h>
#include <linux/icmpv6.h>
#include <linux/icmpv6.h>
#include <linux/init.h>
#include <linux/init.h>
#include <linux/skbuff.h>
#include <asm/uaccess.h>
#include <asm/uaccess.h>


#include <net/sock.h>
#include <net/sock.h>
@@ -300,20 +301,7 @@ out:
	return err;
	return err;


csum_copy_err:
csum_copy_err:
	/* Clear queue. */
	skb_kill_datagram(sk, skb, flags);
	if (flags&MSG_PEEK) {
		int clear = 0;
		spin_lock_bh(&sk->sk_receive_queue.lock);
		if (skb == skb_peek(&sk->sk_receive_queue)) {
			__skb_unlink(skb, &sk->sk_receive_queue);
			clear = 1;
		}
		spin_unlock_bh(&sk->sk_receive_queue.lock);
		if (clear)
			kfree_skb(skb);
	}

	skb_free_datagram(sk, skb);


	if (flags & MSG_DONTWAIT) {
	if (flags & MSG_DONTWAIT) {
		UDP6_INC_STATS_USER(UDP_MIB_INERRORS);
		UDP6_INC_STATS_USER(UDP_MIB_INERRORS);