Commit ea3793ee authored by Rainer Weikusat's avatar Rainer Weikusat Committed by David S. Miller

core: enable more fine-grained datagram reception control

The __skb_recv_datagram routine in core/ datagram.c provides a general
skb reception factility supposed to be utilized by protocol modules
providing datagram sockets. It encompasses both the actual recvmsg code
and a surrounding 'sleep until data is available' loop. This is
inconvenient if a protocol module has to use additional locking in order
to maintain some per-socket state the generic datagram socket code is
unaware of (as the af_unix code does). The patch below moves the recvmsg
proper code into a new __skb_try_recv_datagram routine which doesn't
sleep and renames wait_for_more_packets to
__skb_wait_for_more_packets, both routines being exported interfaces. The
original __skb_recv_datagram routine is reimplemented on top of these
two functions such that its user-visible behaviour remains unchanged.
Signed-off-by: default avatarRainer Weikusat <rweikusat@mobileactivedefense.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 7bf9ae01
...@@ -2785,6 +2785,12 @@ static inline void skb_frag_list_init(struct sk_buff *skb) ...@@ -2785,6 +2785,12 @@ static inline void skb_frag_list_init(struct sk_buff *skb)
#define skb_walk_frags(skb, iter) \ #define skb_walk_frags(skb, iter) \
for (iter = skb_shinfo(skb)->frag_list; iter; iter = iter->next) for (iter = skb_shinfo(skb)->frag_list; iter; iter = iter->next)
int __skb_wait_for_more_packets(struct sock *sk, int *err, long *timeo_p,
const struct sk_buff *skb);
struct sk_buff *__skb_try_recv_datagram(struct sock *sk, unsigned flags,
int *peeked, int *off, int *err,
struct sk_buff **last);
struct sk_buff *__skb_recv_datagram(struct sock *sk, unsigned flags, struct sk_buff *__skb_recv_datagram(struct sock *sk, unsigned flags,
int *peeked, int *off, int *err); int *peeked, int *off, int *err);
struct sk_buff *skb_recv_datagram(struct sock *sk, unsigned flags, int noblock, struct sk_buff *skb_recv_datagram(struct sock *sk, unsigned flags, int noblock,
......
...@@ -83,7 +83,7 @@ static int receiver_wake_function(wait_queue_t *wait, unsigned int mode, int syn ...@@ -83,7 +83,7 @@ static int receiver_wake_function(wait_queue_t *wait, unsigned int mode, int syn
/* /*
* Wait for the last received packet to be different from skb * Wait for the last received packet to be different from skb
*/ */
static int wait_for_more_packets(struct sock *sk, int *err, long *timeo_p, int __skb_wait_for_more_packets(struct sock *sk, int *err, long *timeo_p,
const struct sk_buff *skb) const struct sk_buff *skb)
{ {
int error; int error;
...@@ -130,6 +130,7 @@ static int wait_for_more_packets(struct sock *sk, int *err, long *timeo_p, ...@@ -130,6 +130,7 @@ static int wait_for_more_packets(struct sock *sk, int *err, long *timeo_p,
error = 1; error = 1;
goto out; goto out;
} }
EXPORT_SYMBOL(__skb_wait_for_more_packets);
static struct sk_buff *skb_set_peeked(struct sk_buff *skb) static struct sk_buff *skb_set_peeked(struct sk_buff *skb)
{ {
...@@ -161,13 +162,15 @@ static struct sk_buff *skb_set_peeked(struct sk_buff *skb) ...@@ -161,13 +162,15 @@ static struct sk_buff *skb_set_peeked(struct sk_buff *skb)
} }
/** /**
* __skb_recv_datagram - Receive a datagram skbuff * __skb_try_recv_datagram - Receive a datagram skbuff
* @sk: socket * @sk: socket
* @flags: MSG_ flags * @flags: MSG_ flags
* @peeked: returns non-zero if this packet has been seen before * @peeked: returns non-zero if this packet has been seen before
* @off: an offset in bytes to peek skb from. Returns an offset * @off: an offset in bytes to peek skb from. Returns an offset
* within an skb where data actually starts * within an skb where data actually starts
* @err: error code returned * @err: error code returned
* @last: set to last peeked message to inform the wait function
* what to look for when peeking
* *
* Get a datagram skbuff, understands the peeking, nonblocking wakeups * Get a datagram skbuff, understands the peeking, nonblocking wakeups
* and possible races. This replaces identical code in packet, raw and * and possible races. This replaces identical code in packet, raw and
...@@ -175,9 +178,11 @@ static struct sk_buff *skb_set_peeked(struct sk_buff *skb) ...@@ -175,9 +178,11 @@ static struct sk_buff *skb_set_peeked(struct sk_buff *skb)
* the long standing peek and read race for datagram sockets. If you * the long standing peek and read race for datagram sockets. If you
* alter this routine remember it must be re-entrant. * alter this routine remember it must be re-entrant.
* *
* This function will lock the socket if a skb is returned, so the caller * This function will lock the socket if a skb is returned, so
* needs to unlock the socket in that case (usually by calling * the caller needs to unlock the socket in that case (usually by
* skb_free_datagram) * calling skb_free_datagram). Returns NULL with *err set to
* -EAGAIN if no data was available or to some other value if an
* error was detected.
* *
* * It does not lock socket since today. This function is * * It does not lock socket since today. This function is
* * free of race conditions. This measure should/can improve * * free of race conditions. This measure should/can improve
...@@ -191,13 +196,13 @@ static struct sk_buff *skb_set_peeked(struct sk_buff *skb) ...@@ -191,13 +196,13 @@ static struct sk_buff *skb_set_peeked(struct sk_buff *skb)
* quite explicitly by POSIX 1003.1g, don't change them without having * quite explicitly by POSIX 1003.1g, don't change them without having
* the standard around please. * the standard around please.
*/ */
struct sk_buff *__skb_recv_datagram(struct sock *sk, unsigned int flags, struct sk_buff *__skb_try_recv_datagram(struct sock *sk, unsigned int flags,
int *peeked, int *off, int *err) int *peeked, int *off, int *err,
struct sk_buff **last)
{ {
struct sk_buff_head *queue = &sk->sk_receive_queue; struct sk_buff_head *queue = &sk->sk_receive_queue;
struct sk_buff *skb, *last; struct sk_buff *skb;
unsigned long cpu_flags; unsigned long cpu_flags;
long timeo;
/* /*
* Caller is allowed not to check sk->sk_err before skb_recv_datagram() * Caller is allowed not to check sk->sk_err before skb_recv_datagram()
*/ */
...@@ -206,8 +211,6 @@ struct sk_buff *__skb_recv_datagram(struct sock *sk, unsigned int flags, ...@@ -206,8 +211,6 @@ struct sk_buff *__skb_recv_datagram(struct sock *sk, unsigned int flags,
if (error) if (error)
goto no_packet; goto no_packet;
timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT);
do { do {
/* Again only user level code calls this function, so nothing /* Again only user level code calls this function, so nothing
* interrupt level will suddenly eat the receive_queue. * interrupt level will suddenly eat the receive_queue.
...@@ -217,10 +220,10 @@ struct sk_buff *__skb_recv_datagram(struct sock *sk, unsigned int flags, ...@@ -217,10 +220,10 @@ struct sk_buff *__skb_recv_datagram(struct sock *sk, unsigned int flags,
*/ */
int _off = *off; int _off = *off;
last = (struct sk_buff *)queue; *last = (struct sk_buff *)queue;
spin_lock_irqsave(&queue->lock, cpu_flags); spin_lock_irqsave(&queue->lock, cpu_flags);
skb_queue_walk(queue, skb) { skb_queue_walk(queue, skb) {
last = skb; *last = skb;
*peeked = skb->peeked; *peeked = skb->peeked;
if (flags & MSG_PEEK) { if (flags & MSG_PEEK) {
if (_off >= skb->len && (skb->len || _off || if (_off >= skb->len && (skb->len || _off ||
...@@ -231,8 +234,11 @@ struct sk_buff *__skb_recv_datagram(struct sock *sk, unsigned int flags, ...@@ -231,8 +234,11 @@ struct sk_buff *__skb_recv_datagram(struct sock *sk, unsigned int flags,
skb = skb_set_peeked(skb); skb = skb_set_peeked(skb);
error = PTR_ERR(skb); error = PTR_ERR(skb);
if (IS_ERR(skb)) if (IS_ERR(skb)) {
goto unlock_err; spin_unlock_irqrestore(&queue->lock,
cpu_flags);
goto no_packet;
}
atomic_inc(&skb->users); atomic_inc(&skb->users);
} else } else
...@@ -242,27 +248,40 @@ struct sk_buff *__skb_recv_datagram(struct sock *sk, unsigned int flags, ...@@ -242,27 +248,40 @@ struct sk_buff *__skb_recv_datagram(struct sock *sk, unsigned int flags,
*off = _off; *off = _off;
return skb; return skb;
} }
spin_unlock_irqrestore(&queue->lock, cpu_flags);
if (sk_can_busy_loop(sk) && spin_unlock_irqrestore(&queue->lock, cpu_flags);
sk_busy_loop(sk, flags & MSG_DONTWAIT)) } while (sk_can_busy_loop(sk) &&
continue; sk_busy_loop(sk, flags & MSG_DONTWAIT));
/* User doesn't want to wait */
error = -EAGAIN; error = -EAGAIN;
if (!timeo)
goto no_packet;
} while (!wait_for_more_packets(sk, err, &timeo, last));
return NULL;
unlock_err:
spin_unlock_irqrestore(&queue->lock, cpu_flags);
no_packet: no_packet:
*err = error; *err = error;
return NULL; return NULL;
} }
EXPORT_SYMBOL(__skb_try_recv_datagram);
struct sk_buff *__skb_recv_datagram(struct sock *sk, unsigned int flags,
int *peeked, int *off, int *err)
{
struct sk_buff *skb, *last;
long timeo;
timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT);
do {
skb = __skb_try_recv_datagram(sk, flags, peeked, off, err,
&last);
if (skb)
return skb;
if (*err != EAGAIN)
break;
} while (timeo &&
!__skb_wait_for_more_packets(sk, err, &timeo, last));
return NULL;
}
EXPORT_SYMBOL(__skb_recv_datagram); EXPORT_SYMBOL(__skb_recv_datagram);
struct sk_buff *skb_recv_datagram(struct sock *sk, unsigned int flags, struct sk_buff *skb_recv_datagram(struct sock *sk, unsigned int flags,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment