const union sctp_addr *peer,
struct sctp_transport **pt);
-static void sctp_add_backlog(struct sock *sk, struct sk_buff *skb);
-
/* Calculate the SCTP checksum of an SCTP packet. */
static inline int sctp_rcv_checksum(struct sk_buff *skb)
union sctp_addr dest;
int family;
struct sctp_af *af;
+ int ret = 0;
if (skb->pkt_type!=PACKET_HOST)
goto discard_it;
SCTP_INC_STATS_BH(SCTP_MIB_INSCTPPACKS);
- if (skb_linearize(skb))
+ if (skb_linearize(skb, GFP_ATOMIC))
goto discard_it;
sh = (struct sctphdr *) skb->h.raw;
__skb_pull(skb, skb->h.raw - skb->data);
if (skb->len < sizeof(struct sctphdr))
goto discard_it;
- if ((skb->ip_summed != CHECKSUM_UNNECESSARY) &&
- (sctp_rcv_checksum(skb) < 0))
+ if (sctp_rcv_checksum(skb) < 0)
goto discard_it;
skb_pull(skb, sizeof(struct sctphdr));
*/
if (sk->sk_bound_dev_if && (sk->sk_bound_dev_if != af->skb_iif(skb)))
{
+ sock_put(sk);
if (asoc) {
sctp_association_put(asoc);
asoc = NULL;
sk = sctp_get_ctl_sock();
ep = sctp_sk(sk)->ep;
sctp_endpoint_hold(ep);
+ sock_hold(sk);
rcvr = &ep->base;
}
goto discard_release;
nf_reset(skb);
- if (sk_filter(sk, skb, 1))
+ ret = sk_filter(sk, skb, 1);
+ if (ret)
goto discard_release;
/* Create an SCTP packet structure. */
chunk = sctp_chunkify(skb, asoc, sk);
- if (!chunk)
+ if (!chunk) {
+ ret = -ENOMEM;
goto discard_release;
+ }
SCTP_INPUT_CB(skb)->chunk = chunk;
/* Remember what endpoint is to handle this packet. */
*/
sctp_bh_lock_sock(sk);
+ /* It is possible that the association could have moved to a different
+ * socket if it is peeled off. If so, update the sk.
+ */
+ if (sk != rcvr->sk) {
+ sctp_bh_lock_sock(rcvr->sk);
+ sctp_bh_unlock_sock(sk);
+ sk = rcvr->sk;
+ }
+
if (sock_owned_by_user(sk))
- sctp_add_backlog(sk, skb);
+ sk_add_backlog(sk, skb);
else
- sctp_inq_push(&chunk->rcvr->inqueue, chunk);
+ sctp_backlog_rcv(sk, skb);
+ /* Release the sock and the sock ref we took in the lookup calls.
+ * The asoc/ep ref will be released in sctp_backlog_rcv.
+ */
sctp_bh_unlock_sock(sk);
+ sock_put(sk);
- /* Release the asoc/ep ref we took in the lookup calls. */
- if (asoc)
- sctp_association_put(asoc);
- else
- sctp_endpoint_put(ep);
-
- return 0;
+ return ret;
discard_it:
kfree_skb(skb);
- return 0;
+ return ret;
discard_release:
- /* Release the asoc/ep ref we took in the lookup calls. */
+ /* Release any structures we may be holding. */
+ sock_put(sk);
if (asoc)
sctp_association_put(asoc);
else
goto discard_it;
}
-/* Process the backlog queue of the socket. Every skb on
- * the backlog holds a ref on an association or endpoint.
- * We hold this ref throughout the state machine to make
- * sure that the structure we need is still around.
+/* Handle second half of inbound skb processing. If the sock was busy,
+ * we may have need to delay processing until later when the sock is
+ * released (on the backlog). If not busy, we call this routine
+ * directly from the bottom half.
*/
int sctp_backlog_rcv(struct sock *sk, struct sk_buff *skb)
{
struct sctp_chunk *chunk = SCTP_INPUT_CB(skb)->chunk;
- struct sctp_inq *inqueue = &chunk->rcvr->inqueue;
+ struct sctp_inq *inqueue = NULL;
struct sctp_ep_common *rcvr = NULL;
- int backloged = 0;
rcvr = chunk->rcvr;
- /* If the rcvr is dead then the association or endpoint
- * has been deleted and we can safely drop the chunk
- * and refs that we are holding.
- */
- if (rcvr->dead) {
- sctp_chunk_free(chunk);
- goto done;
- }
-
- if (unlikely(rcvr->sk != sk)) {
- /* In this case, the association moved from one socket to
- * another. We are currently sitting on the backlog of the
- * old socket, so we need to move.
- * However, since we are here in the process context we
- * need to take make sure that the user doesn't own
- * the new socket when we process the packet.
- * If the new socket is user-owned, queue the chunk to the
- * backlog of the new socket without dropping any refs.
- * Otherwise, we can safely push the chunk on the inqueue.
- */
-
- sk = rcvr->sk;
- sctp_bh_lock_sock(sk);
-
- if (sock_owned_by_user(sk)) {
- sk_add_backlog(sk, skb);
- backloged = 1;
- } else
- sctp_inq_push(inqueue, chunk);
-
- sctp_bh_unlock_sock(sk);
-
- /* If the chunk was backloged again, don't drop refs */
- if (backloged)
- return 0;
- } else {
- sctp_inq_push(inqueue, chunk);
- }
-
-done:
- /* Release the refs we took in sctp_add_backlog */
- if (SCTP_EP_TYPE_ASSOCIATION == rcvr->type)
- sctp_association_put(sctp_assoc(rcvr));
- else if (SCTP_EP_TYPE_SOCKET == rcvr->type)
- sctp_endpoint_put(sctp_ep(rcvr));
- else
- BUG();
-
+ BUG_TRAP(rcvr->sk == sk);
+
+ if (rcvr->dead) {
+ sctp_chunk_free(chunk);
+ } else {
+ inqueue = &chunk->rcvr->inqueue;
+ sctp_inq_push(inqueue, chunk);
+ }
+
+ /* Release the asoc/ep ref we took in the lookup calls in sctp_rcv. */
+ if (SCTP_EP_TYPE_ASSOCIATION == rcvr->type)
+ sctp_association_put(sctp_assoc(rcvr));
+ else
+ sctp_endpoint_put(sctp_ep(rcvr));
+
return 0;
}
-static void sctp_add_backlog(struct sock *sk, struct sk_buff *skb)
+void sctp_backlog_migrate(struct sctp_association *assoc,
+ struct sock *oldsk, struct sock *newsk)
{
- struct sctp_chunk *chunk = SCTP_INPUT_CB(skb)->chunk;
- struct sctp_ep_common *rcvr = chunk->rcvr;
-
- /* Hold the assoc/ep while hanging on the backlog queue.
- * This way, we know structures we need will not disappear from us
- */
- if (SCTP_EP_TYPE_ASSOCIATION == rcvr->type)
- sctp_association_hold(sctp_assoc(rcvr));
- else if (SCTP_EP_TYPE_SOCKET == rcvr->type)
- sctp_endpoint_hold(sctp_ep(rcvr));
- else
- BUG();
+ struct sk_buff *skb;
+ struct sctp_chunk *chunk;
- sk_add_backlog(sk, skb);
+ skb = oldsk->sk_backlog.head;
+ oldsk->sk_backlog.head = oldsk->sk_backlog.tail = NULL;
+ while (skb != NULL) {
+ struct sk_buff *next = skb->next;
+
+ chunk = SCTP_INPUT_CB(skb)->chunk;
+ skb->next = NULL;
+ if (&assoc->base == chunk->rcvr)
+ sk_add_backlog(newsk, skb);
+ else
+ sk_add_backlog(oldsk, skb);
+ skb = next;
+ }
}
/* Handle icmp frag needed error. */
union sctp_addr daddr;
struct sctp_af *af;
struct sock *sk = NULL;
- struct sctp_association *asoc;
+ struct sctp_association *asoc = NULL;
struct sctp_transport *transport = NULL;
*app = NULL; *tpp = NULL;
return sk;
out:
+ sock_put(sk);
if (asoc)
sctp_association_put(asoc);
return NULL;
void sctp_err_finish(struct sock *sk, struct sctp_association *asoc)
{
sctp_bh_unlock_sock(sk);
+ sock_put(sk);
if (asoc)
sctp_association_put(asoc);
}
int type = skb->h.icmph->type;
int code = skb->h.icmph->code;
struct sock *sk;
- struct sctp_association *asoc = NULL;
+ struct sctp_association *asoc;
struct sctp_transport *transport;
struct inet_sock *inet;
char *saveip, *savesctp;
hit:
sctp_endpoint_hold(ep);
+ sock_hold(epb->sk);
read_unlock(&head->lock);
return ep;
}
hit:
*pt = transport;
sctp_association_hold(asoc);
+ sock_hold(epb->sk);
read_unlock(&head->lock);
return asoc;
}
struct sctp_transport *transport;
if ((asoc = sctp_lookup_association(laddr, paddr, &transport))) {
+ sock_put(asoc->base.sk);
sctp_association_put(asoc);
return 1;
}