linux 2.6.16.38 w/ vs2.0.3-rc1
[linux-2.6.git] / net / sctp / input.c
index 2060bbe..2325fee 100644 (file)
@@ -73,8 +73,6 @@ static struct sctp_association *__sctp_lookup_association(
                                        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)
@@ -129,13 +127,14 @@ int sctp_rcv(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;
@@ -144,8 +143,7 @@ int sctp_rcv(struct sk_buff *skb)
        __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));
@@ -193,6 +191,7 @@ int sctp_rcv(struct sk_buff *skb)
         */
        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;
@@ -203,6 +202,7 @@ int sctp_rcv(struct sk_buff *skb)
                sk = sctp_get_ctl_sock();
                ep = sctp_sk(sk)->ep;
                sctp_endpoint_hold(ep);
+               sock_hold(sk);
                rcvr = &ep->base;
        }
 
@@ -231,13 +231,16 @@ int sctp_rcv(struct sk_buff *skb)
                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. */
@@ -258,27 +261,35 @@ int sctp_rcv(struct sk_buff *skb)
         */
        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
@@ -287,87 +298,56 @@ discard_release:
        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. */
@@ -440,7 +420,7 @@ struct sock *sctp_err_lookup(int family, struct sk_buff *skb,
        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;
@@ -481,6 +461,7 @@ struct sock *sctp_err_lookup(int family, struct sk_buff *skb,
        return sk;
 
 out:
+       sock_put(sk);
        if (asoc)
                sctp_association_put(asoc);
        return NULL;
@@ -490,6 +471,7 @@ out:
 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);
 }
@@ -516,7 +498,7 @@ void sctp_v4_err(struct sk_buff *skb, __u32 info)
        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;
@@ -742,6 +724,7 @@ static struct sctp_endpoint *__sctp_rcv_lookup_endpoint(const union sctp_addr *l
 
 hit:
        sctp_endpoint_hold(ep);
+       sock_hold(epb->sk);
        read_unlock(&head->lock);
        return ep;
 }
@@ -843,6 +826,7 @@ static struct sctp_association *__sctp_lookup_association(
 hit:
        *pt = transport;
        sctp_association_hold(asoc);
+       sock_hold(epb->sk);
        read_unlock(&head->lock);
        return asoc;
 }
@@ -870,6 +854,7 @@ int sctp_has_association(const union sctp_addr *laddr,
        struct sctp_transport *transport;
 
        if ((asoc = sctp_lookup_association(laddr, paddr, &transport))) {
+               sock_put(asoc->base.sk);
                sctp_association_put(asoc);
                return 1;
        }