Commit b6e1331f authored by Vlad Yasevich's avatar Vlad Yasevich Committed by David S. Miller

[SCTP]: Implement SCTP_FRAGMENT_INTERLEAVE socket option

This option was introduced in draft-ietf-tsvwg-sctpsocket-13.  It
prevents head-of-line blocking in the case of one-to-many endpoint.
Applications enabling this option really must enable SCTP_SNDRCV event
so that they would know where the data belongs.  Based on an
earlier patch by Ivan Skytte Jørgensen.

Additionally, this functionality now permits multiple associations
on the same endpoint to enter Partial Delivery.  Applications should
be extra careful, when using this functionality, to track EOR indicators.
Signed-off-by: default avatarVlad Yasevich <vladislav.yasevich@hp.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent c95e9395
...@@ -304,10 +304,11 @@ struct sctp_sock { ...@@ -304,10 +304,11 @@ struct sctp_sock {
__u32 autoclose; __u32 autoclose;
__u8 nodelay; __u8 nodelay;
__u8 disable_fragments; __u8 disable_fragments;
__u8 pd_mode;
__u8 v4mapped; __u8 v4mapped;
__u8 frag_interleave;
__u32 adaptation_ind; __u32 adaptation_ind;
atomic_t pd_mode;
/* Receive to here while partial delivery is in effect. */ /* Receive to here while partial delivery is in effect. */
struct sk_buff_head pd_lobby; struct sk_buff_head pd_lobby;
}; };
......
...@@ -78,7 +78,7 @@ void sctp_ulpq_partial_delivery(struct sctp_ulpq *, struct sctp_chunk *, gfp_t); ...@@ -78,7 +78,7 @@ void sctp_ulpq_partial_delivery(struct sctp_ulpq *, struct sctp_chunk *, gfp_t);
void sctp_ulpq_abort_pd(struct sctp_ulpq *, gfp_t); void sctp_ulpq_abort_pd(struct sctp_ulpq *, gfp_t);
/* Clear the partial data delivery condition on this socket. */ /* Clear the partial data delivery condition on this socket. */
int sctp_clear_pd(struct sock *sk); int sctp_clear_pd(struct sock *sk, struct sctp_association *asoc);
/* Skip over an SSN. */ /* Skip over an SSN. */
void sctp_ulpq_skip(struct sctp_ulpq *ulpq, __u16 sid, __u16 ssn); void sctp_ulpq_skip(struct sctp_ulpq *ulpq, __u16 sid, __u16 ssn);
......
...@@ -97,6 +97,8 @@ enum sctp_optname { ...@@ -97,6 +97,8 @@ enum sctp_optname {
#define SCTP_DELAYED_ACK_TIME SCTP_DELAYED_ACK_TIME #define SCTP_DELAYED_ACK_TIME SCTP_DELAYED_ACK_TIME
SCTP_CONTEXT, /* Receive Context */ SCTP_CONTEXT, /* Receive Context */
#define SCTP_CONTEXT SCTP_CONTEXT #define SCTP_CONTEXT SCTP_CONTEXT
SCTP_FRAGMENT_INTERLEAVE,
#define SCTP_FRAGMENT_INTERLEAVE SCTP_FRAGMENT_INTERLEAVE
/* Internal Socket Options. Some of the sctp library functions are /* Internal Socket Options. Some of the sctp library functions are
* implemented using these socket options. * implemented using these socket options.
...@@ -530,7 +532,7 @@ struct sctp_paddrparams { ...@@ -530,7 +532,7 @@ struct sctp_paddrparams {
__u32 spp_flags; __u32 spp_flags;
} __attribute__((packed, aligned(4))); } __attribute__((packed, aligned(4)));
/* 7.1.24. Delayed Ack Timer (SCTP_DELAYED_ACK_TIME) /* 7.1.23. Delayed Ack Timer (SCTP_DELAYED_ACK_TIME)
* *
* This options will get or set the delayed ack timer. The time is set * This options will get or set the delayed ack timer. The time is set
* in milliseconds. If the assoc_id is 0, then this sets or gets the * in milliseconds. If the assoc_id is 0, then this sets or gets the
......
...@@ -2255,7 +2255,7 @@ static int sctp_setsockopt_peer_addr_params(struct sock *sk, ...@@ -2255,7 +2255,7 @@ static int sctp_setsockopt_peer_addr_params(struct sock *sk,
return 0; return 0;
} }
/* 7.1.24. Delayed Ack Timer (SCTP_DELAYED_ACK_TIME) /* 7.1.23. Delayed Ack Timer (SCTP_DELAYED_ACK_TIME)
* *
* This options will get or set the delayed ack timer. The time is set * This options will get or set the delayed ack timer. The time is set
* in milliseconds. If the assoc_id is 0, then this sets or gets the * in milliseconds. If the assoc_id is 0, then this sets or gets the
...@@ -2792,6 +2792,46 @@ static int sctp_setsockopt_context(struct sock *sk, char __user *optval, ...@@ -2792,6 +2792,46 @@ static int sctp_setsockopt_context(struct sock *sk, char __user *optval,
return 0; return 0;
} }
/*
* 7.1.24. Get or set fragmented interleave (SCTP_FRAGMENT_INTERLEAVE)
*
* This options will at a minimum specify if the implementation is doing
* fragmented interleave. Fragmented interleave, for a one to many
* socket, is when subsequent calls to receive a message may return
* parts of messages from different associations. Some implementations
* may allow you to turn this value on or off. If so, when turned off,
* no fragment interleave will occur (which will cause a head of line
* blocking amongst multiple associations sharing the same one to many
* socket). When this option is turned on, then each receive call may
* come from a different association (thus the user must receive data
* with the extended calls (e.g. sctp_recvmsg) to keep track of which
* association each receive belongs to.
*
* This option takes a boolean value. A non-zero value indicates that
* fragmented interleave is on. A value of zero indicates that
* fragmented interleave is off.
*
* Note that it is important that an implementation that allows this
* option to be turned on, have it off by default. Otherwise an unaware
* application using the one to many model may become confused and act
* incorrectly.
*/
static int sctp_setsockopt_fragment_interleave(struct sock *sk,
char __user *optval,
int optlen)
{
int val;
if (optlen != sizeof(int))
return -EINVAL;
if (get_user(val, (int __user *)optval))
return -EFAULT;
sctp_sk(sk)->frag_interleave = (val == 0) ? 0 : 1;
return 0;
}
/* API 6.2 setsockopt(), getsockopt() /* API 6.2 setsockopt(), getsockopt()
* *
* Applications use setsockopt() and getsockopt() to set or retrieve * Applications use setsockopt() and getsockopt() to set or retrieve
...@@ -2906,7 +2946,9 @@ SCTP_STATIC int sctp_setsockopt(struct sock *sk, int level, int optname, ...@@ -2906,7 +2946,9 @@ SCTP_STATIC int sctp_setsockopt(struct sock *sk, int level, int optname,
case SCTP_CONTEXT: case SCTP_CONTEXT:
retval = sctp_setsockopt_context(sk, optval, optlen); retval = sctp_setsockopt_context(sk, optval, optlen);
break; break;
case SCTP_FRAGMENT_INTERLEAVE:
retval = sctp_setsockopt_fragment_interleave(sk, optval, optlen);
break;
default: default:
retval = -ENOPROTOOPT; retval = -ENOPROTOOPT;
break; break;
...@@ -3134,8 +3176,9 @@ SCTP_STATIC int sctp_init_sock(struct sock *sk) ...@@ -3134,8 +3176,9 @@ SCTP_STATIC int sctp_init_sock(struct sock *sk)
sp->pf = sctp_get_pf_specific(sk->sk_family); sp->pf = sctp_get_pf_specific(sk->sk_family);
/* Control variables for partial data delivery. */ /* Control variables for partial data delivery. */
sp->pd_mode = 0; atomic_set(&sp->pd_mode, 0);
skb_queue_head_init(&sp->pd_lobby); skb_queue_head_init(&sp->pd_lobby);
sp->frag_interleave = 0;
/* Create a per socket endpoint structure. Even if we /* Create a per socket endpoint structure. Even if we
* change the data structure relationships, this may still * change the data structure relationships, this may still
...@@ -3642,7 +3685,7 @@ static int sctp_getsockopt_peer_addr_params(struct sock *sk, int len, ...@@ -3642,7 +3685,7 @@ static int sctp_getsockopt_peer_addr_params(struct sock *sk, int len,
return 0; return 0;
} }
/* 7.1.24. Delayed Ack Timer (SCTP_DELAYED_ACK_TIME) /* 7.1.23. Delayed Ack Timer (SCTP_DELAYED_ACK_TIME)
* *
* This options will get or set the delayed ack timer. The time is set * This options will get or set the delayed ack timer. The time is set
* in milliseconds. If the assoc_id is 0, then this sets or gets the * in milliseconds. If the assoc_id is 0, then this sets or gets the
...@@ -4536,6 +4579,29 @@ static int sctp_getsockopt_maxseg(struct sock *sk, int len, ...@@ -4536,6 +4579,29 @@ static int sctp_getsockopt_maxseg(struct sock *sk, int len,
return 0; return 0;
} }
/*
* 7.1.24. Get or set fragmented interleave (SCTP_FRAGMENT_INTERLEAVE)
* (chapter and verse is quoted at sctp_setsockopt_fragment_interleave())
*/
static int sctp_getsockopt_fragment_interleave(struct sock *sk, int len,
char __user *optval, int __user *optlen)
{
int val;
if (len < sizeof(int))
return -EINVAL;
len = sizeof(int);
val = sctp_sk(sk)->frag_interleave;
if (put_user(len, optlen))
return -EFAULT;
if (copy_to_user(optval, &val, len))
return -EFAULT;
return 0;
}
SCTP_STATIC int sctp_getsockopt(struct sock *sk, int level, int optname, SCTP_STATIC int sctp_getsockopt(struct sock *sk, int level, int optname,
char __user *optval, int __user *optlen) char __user *optval, int __user *optlen)
{ {
...@@ -4648,6 +4714,10 @@ SCTP_STATIC int sctp_getsockopt(struct sock *sk, int level, int optname, ...@@ -4648,6 +4714,10 @@ SCTP_STATIC int sctp_getsockopt(struct sock *sk, int level, int optname,
case SCTP_CONTEXT: case SCTP_CONTEXT:
retval = sctp_getsockopt_context(sk, len, optval, optlen); retval = sctp_getsockopt_context(sk, len, optval, optlen);
break; break;
case SCTP_FRAGMENT_INTERLEAVE:
retval = sctp_getsockopt_fragment_interleave(sk, len, optval,
optlen);
break;
default: default:
retval = -ENOPROTOOPT; retval = -ENOPROTOOPT;
break; break;
...@@ -5742,9 +5812,9 @@ static void sctp_sock_migrate(struct sock *oldsk, struct sock *newsk, ...@@ -5742,9 +5812,9 @@ static void sctp_sock_migrate(struct sock *oldsk, struct sock *newsk,
* 3) Peeling off non-partial delivery; move pd_lobby to receive_queue. * 3) Peeling off non-partial delivery; move pd_lobby to receive_queue.
*/ */
skb_queue_head_init(&newsp->pd_lobby); skb_queue_head_init(&newsp->pd_lobby);
sctp_sk(newsk)->pd_mode = assoc->ulpq.pd_mode; atomic_set(&sctp_sk(newsk)->pd_mode, assoc->ulpq.pd_mode);
if (sctp_sk(oldsk)->pd_mode) { if (atomic_read(&sctp_sk(oldsk)->pd_mode)) {
struct sk_buff_head *queue; struct sk_buff_head *queue;
/* Decide which queue to move pd_lobby skbs to. */ /* Decide which queue to move pd_lobby skbs to. */
...@@ -5770,7 +5840,7 @@ static void sctp_sock_migrate(struct sock *oldsk, struct sock *newsk, ...@@ -5770,7 +5840,7 @@ static void sctp_sock_migrate(struct sock *oldsk, struct sock *newsk,
* delivery to finish. * delivery to finish.
*/ */
if (assoc->ulpq.pd_mode) if (assoc->ulpq.pd_mode)
sctp_clear_pd(oldsk); sctp_clear_pd(oldsk, NULL);
} }
......
...@@ -138,11 +138,14 @@ int sctp_ulpq_tail_data(struct sctp_ulpq *ulpq, struct sctp_chunk *chunk, ...@@ -138,11 +138,14 @@ int sctp_ulpq_tail_data(struct sctp_ulpq *ulpq, struct sctp_chunk *chunk,
/* Clear the partial delivery mode for this socket. Note: This /* Clear the partial delivery mode for this socket. Note: This
* assumes that no association is currently in partial delivery mode. * assumes that no association is currently in partial delivery mode.
*/ */
int sctp_clear_pd(struct sock *sk) int sctp_clear_pd(struct sock *sk, struct sctp_association *asoc)
{ {
struct sctp_sock *sp = sctp_sk(sk); struct sctp_sock *sp = sctp_sk(sk);
sp->pd_mode = 0; if (atomic_dec_and_test(&sp->pd_mode)) {
/* This means there are no other associations in PD, so
* we can go ahead and clear out the lobby in one shot
*/
if (!skb_queue_empty(&sp->pd_lobby)) { if (!skb_queue_empty(&sp->pd_lobby)) {
struct list_head *list; struct list_head *list;
sctp_skb_list_tail(&sp->pd_lobby, &sk->sk_receive_queue); sctp_skb_list_tail(&sp->pd_lobby, &sk->sk_receive_queue);
...@@ -150,6 +153,27 @@ int sctp_clear_pd(struct sock *sk) ...@@ -150,6 +153,27 @@ int sctp_clear_pd(struct sock *sk)
INIT_LIST_HEAD(list); INIT_LIST_HEAD(list);
return 1; return 1;
} }
} else {
/* There are other associations in PD, so we only need to
* pull stuff out of the lobby that belongs to the
* associations that is exiting PD (all of its notifications
* are posted here).
*/
if (!skb_queue_empty(&sp->pd_lobby) && asoc) {
struct sk_buff *skb, *tmp;
struct sctp_ulpevent *event;
sctp_skb_for_each(skb, &sp->pd_lobby, tmp) {
event = sctp_skb2event(skb);
if (event->asoc == asoc) {
__skb_unlink(skb, &sp->pd_lobby);
__skb_queue_tail(&sk->sk_receive_queue,
skb);
}
}
}
}
return 0; return 0;
} }
...@@ -157,7 +181,7 @@ int sctp_clear_pd(struct sock *sk) ...@@ -157,7 +181,7 @@ int sctp_clear_pd(struct sock *sk)
static int sctp_ulpq_clear_pd(struct sctp_ulpq *ulpq) static int sctp_ulpq_clear_pd(struct sctp_ulpq *ulpq)
{ {
ulpq->pd_mode = 0; ulpq->pd_mode = 0;
return sctp_clear_pd(ulpq->asoc->base.sk); return sctp_clear_pd(ulpq->asoc->base.sk, ulpq->asoc);
} }
/* If the SKB of 'event' is on a list, it is the first such member /* If the SKB of 'event' is on a list, it is the first such member
...@@ -187,9 +211,10 @@ int sctp_ulpq_tail_event(struct sctp_ulpq *ulpq, struct sctp_ulpevent *event) ...@@ -187,9 +211,10 @@ int sctp_ulpq_tail_event(struct sctp_ulpq *ulpq, struct sctp_ulpevent *event)
* the association the cause of the partial delivery. * the association the cause of the partial delivery.
*/ */
if (!sctp_sk(sk)->pd_mode) { if (atomic_read(&sctp_sk(sk)->pd_mode) == 0) {
queue = &sk->sk_receive_queue; queue = &sk->sk_receive_queue;
} else if (ulpq->pd_mode) { } else {
if (ulpq->pd_mode) {
/* If the association is in partial delivery, we /* If the association is in partial delivery, we
* need to finish delivering the partially processed * need to finish delivering the partially processed
* packet before passing any other data. This is * packet before passing any other data. This is
...@@ -203,9 +228,18 @@ int sctp_ulpq_tail_event(struct sctp_ulpq *ulpq, struct sctp_ulpevent *event) ...@@ -203,9 +228,18 @@ int sctp_ulpq_tail_event(struct sctp_ulpq *ulpq, struct sctp_ulpevent *event)
clear_pd = event->msg_flags & MSG_EOR; clear_pd = event->msg_flags & MSG_EOR;
queue = &sk->sk_receive_queue; queue = &sk->sk_receive_queue;
} }
} else } else {
/*
* If fragment interleave is enabled, we
* can queue this to the recieve queue instead
* of the lobby.
*/
if (sctp_sk(sk)->frag_interleave)
queue = &sk->sk_receive_queue;
else
queue = &sctp_sk(sk)->pd_lobby; queue = &sctp_sk(sk)->pd_lobby;
}
}
/* If we are harvesting multiple skbs they will be /* If we are harvesting multiple skbs they will be
* collected on a list. * collected on a list.
...@@ -826,18 +860,29 @@ void sctp_ulpq_partial_delivery(struct sctp_ulpq *ulpq, ...@@ -826,18 +860,29 @@ void sctp_ulpq_partial_delivery(struct sctp_ulpq *ulpq,
{ {
struct sctp_ulpevent *event; struct sctp_ulpevent *event;
struct sctp_association *asoc; struct sctp_association *asoc;
struct sctp_sock *sp;
asoc = ulpq->asoc; asoc = ulpq->asoc;
sp = sctp_sk(asoc->base.sk);
/* Are we already in partial delivery mode? */ /* If the association is already in Partial Delivery mode
if (!sctp_sk(asoc->base.sk)->pd_mode) { * we have noting to do.
*/
if (ulpq->pd_mode)
return;
/* If the user enabled fragment interleave socket option,
* multiple associations can enter partial delivery.
* Otherwise, we can only enter partial delivery if the
* socket is not in partial deliver mode.
*/
if (sp->frag_interleave || atomic_read(&sp->pd_mode) == 0) {
/* Is partial delivery possible? */ /* Is partial delivery possible? */
event = sctp_ulpq_retrieve_first(ulpq); event = sctp_ulpq_retrieve_first(ulpq);
/* Send event to the ULP. */ /* Send event to the ULP. */
if (event) { if (event) {
sctp_ulpq_tail_event(ulpq, event); sctp_ulpq_tail_event(ulpq, event);
sctp_sk(asoc->base.sk)->pd_mode = 1; atomic_inc(&sp->pd_mode);
ulpq->pd_mode = 1; ulpq->pd_mode = 1;
return; return;
} }
......
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