linux 2.6.16.38 w/ vs2.0.3-rc1
[linux-2.6.git] / net / sctp / input.c
index 0268f75..2325fee 100644 (file)
 
 /* Forward declarations for internal helpers. */
 static int sctp_rcv_ootb(struct sk_buff *);
-struct sctp_association *__sctp_rcv_lookup(struct sk_buff *skb,
+static struct sctp_association *__sctp_rcv_lookup(struct sk_buff *skb,
                                      const union sctp_addr *laddr,
                                      const union sctp_addr *paddr,
                                      struct sctp_transport **transportp);
-struct sctp_endpoint *__sctp_rcv_lookup_endpoint(const union sctp_addr *laddr);
+static struct sctp_endpoint *__sctp_rcv_lookup_endpoint(const union sctp_addr *laddr);
+static struct sctp_association *__sctp_lookup_association(
+                                       const union sctp_addr *local,
+                                       const union sctp_addr *peer,
+                                       struct sctp_transport **pt);
 
 
 /* Calculate the SCTP checksum of an SCTP packet.  */
@@ -96,6 +100,17 @@ static inline int sctp_rcv_checksum(struct sk_buff *skb)
        return 0;
 }
 
+struct sctp_input_cb {
+       union {
+               struct inet_skb_parm    h4;
+#if defined(CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
+               struct inet6_skb_parm   h6;
+#endif
+       } header;
+       struct sctp_chunk *chunk;
+};
+#define SCTP_INPUT_CB(__skb)   ((struct sctp_input_cb *)&((__skb)->cb[0]))
+
 /*
  * This is the routine which IP calls when receiving an SCTP packet.
  */
@@ -119,6 +134,9 @@ int sctp_rcv(struct sk_buff *skb)
 
        SCTP_INC_STATS_BH(SCTP_MIB_INSCTPPACKS);
 
+       if (skb_linearize(skb, GFP_ATOMIC))
+               goto discard_it;
+
        sh = (struct sctphdr *) skb->h.raw;
 
        /* Pull up the IP and SCTP headers. */
@@ -130,6 +148,10 @@ int sctp_rcv(struct sk_buff *skb)
 
        skb_pull(skb, sizeof(struct sctphdr));
 
+       /* Make sure we at least have chunk headers worth of data left. */
+       if (skb->len < sizeof(struct sctp_chunkhdr))
+               goto discard_it;
+
        family = ipver2af(skb->nh.iph->version);
        af = sctp_get_af_specific(family);
        if (unlikely(!af))
@@ -150,11 +172,40 @@ int sctp_rcv(struct sk_buff *skb)
         * IP broadcast addresses cannot be used in an SCTP transport
         * address."
         */
-       if (!af->addr_valid(&src, NULL) || !af->addr_valid(&dest, NULL))
+       if (!af->addr_valid(&src, NULL, skb) ||
+           !af->addr_valid(&dest, NULL, skb))
                goto discard_it;
 
        asoc = __sctp_rcv_lookup(skb, &src, &dest, &transport);
 
+       if (!asoc)
+               ep = __sctp_rcv_lookup_endpoint(&dest);
+
+       /* Retrieve the common input handling substructure. */
+       rcvr = asoc ? &asoc->base : &ep->base;
+       sk = rcvr->sk;
+
+       /*
+        * If a frame arrives on an interface and the receiving socket is
+        * bound to another interface, via SO_BINDTODEVICE, treat it as OOTB
+        */
+       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;
+               } else {
+                       sctp_endpoint_put(ep);
+                       ep = NULL;
+               }
+               sk = sctp_get_ctl_sock();
+               ep = sctp_sk(sk)->ep;
+               sctp_endpoint_hold(ep);
+               sock_hold(sk);
+               rcvr = &ep->base;
+       }
+
        /*
         * RFC 2960, 8.4 - Handle "Out of the blue" Packets.
         * An SCTP packet is called an "out of the blue" (OOTB)
@@ -164,25 +215,21 @@ int sctp_rcv(struct sk_buff *skb)
         * packet belongs.
         */
        if (!asoc) {
-               ep = __sctp_rcv_lookup_endpoint(&dest);
                if (sctp_rcv_ootb(skb)) {
                        SCTP_INC_STATS_BH(SCTP_MIB_OUTOFBLUES);
                        goto discard_release;
                }
        }
 
-       /* Retrieve the common input handling substructure. */
-       rcvr = asoc ? &asoc->base : &ep->base;
-       sk = rcvr->sk;
-
        /* SCTP seems to always need a timestamp right now (FIXME) */
-       if (skb->stamp.tv_sec == 0) {
-               do_gettimeofday(&skb->stamp);
+       if (skb->tstamp.off_sec == 0) {
+               __net_timestamp(skb);
                sock_enable_timestamp(sk); 
        }
 
        if (!xfrm_policy_check(sk, XFRM_POLICY_IN, skb, family))
                goto discard_release;
+       nf_reset(skb);
 
        ret = sk_filter(sk, skb, 1);
        if (ret)
@@ -194,6 +241,7 @@ int sctp_rcv(struct sk_buff *skb)
                ret = -ENOMEM;
                goto discard_release;
        }
+       SCTP_INPUT_CB(skb)->chunk = chunk;
 
        /* Remember what endpoint is to handle this packet. */
        chunk->rcvr = rcvr;
@@ -213,20 +261,26 @@ 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))
-               sk_add_backlog(sk, (struct sk_buff *) chunk);
+               sk_add_backlog(sk, skb);
        else
-               sctp_backlog_rcv(sk, (struct sk_buff *) chunk);
+               sctp_backlog_rcv(sk, skb);
 
-       /* Release the sock and any reference counts we took in the
-        * lookup calls.
+       /* 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);
-       if (asoc)
-               sctp_association_put(asoc);
-       else
-               sctp_endpoint_put(ep);
        sock_put(sk);
+
        return ret;
 
 discard_it:
@@ -235,13 +289,11 @@ discard_it:
 
 discard_release:
        /* Release any structures we may be holding. */
-       if (asoc) {
-               sock_put(asoc->base.sk);
+       sock_put(sk);
+       if (asoc)
                sctp_association_put(asoc);
-       } else {
-               sock_put(ep->base.sk);
+       else
                sctp_endpoint_put(ep);
-       }
 
        goto discard_it;
 }
@@ -253,41 +305,114 @@ discard_release:
  */
 int sctp_backlog_rcv(struct sock *sk, struct sk_buff *skb)
 {
-       struct sctp_chunk *chunk;
-       struct sctp_inq *inqueue;
+       struct sctp_chunk *chunk = SCTP_INPUT_CB(skb)->chunk;
+       struct sctp_inq *inqueue = NULL;
+       struct sctp_ep_common *rcvr = NULL;
+
+       rcvr = chunk->rcvr;
+
+       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;
+}
 
-       /* One day chunk will live inside the skb, but for
-        * now this works.
-        */
-       chunk = (struct sctp_chunk *) skb;
-       inqueue = &chunk->rcvr->inqueue;
+void sctp_backlog_migrate(struct sctp_association *assoc, 
+                         struct sock *oldsk, struct sock *newsk)
+{
+       struct sk_buff *skb;
+       struct sctp_chunk *chunk;
 
-       sctp_inq_push(inqueue, chunk);
-        return 0;
+       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. */
 void sctp_icmp_frag_needed(struct sock *sk, struct sctp_association *asoc,
                           struct sctp_transport *t, __u32 pmtu)
 {
-       if (unlikely(pmtu < SCTP_DEFAULT_MINSEGMENT)) {
-               printk(KERN_WARNING "%s: Reported pmtu %d too low, "
-                      "using default minimum of %d\n", __FUNCTION__, pmtu,
-                      SCTP_DEFAULT_MINSEGMENT);
-               pmtu = SCTP_DEFAULT_MINSEGMENT;
-       }
+       if (sock_owned_by_user(sk) || !t || (t->pathmtu == pmtu))
+               return;
+
+       if (t->param_flags & SPP_PMTUD_ENABLE) {
+               if (unlikely(pmtu < SCTP_DEFAULT_MINSEGMENT)) {
+                       printk(KERN_WARNING "%s: Reported pmtu %d too low, "
+                              "using default minimum of %d\n",
+                              __FUNCTION__, pmtu,
+                              SCTP_DEFAULT_MINSEGMENT);
+                       /* Use default minimum segment size and disable
+                        * pmtu discovery on this transport.
+                        */
+                       t->pathmtu = SCTP_DEFAULT_MINSEGMENT;
+                       t->param_flags = (t->param_flags & ~SPP_HB) |
+                               SPP_PMTUD_DISABLE;
+               } else {
+                       t->pathmtu = pmtu;
+               }
 
-       if (!sock_owned_by_user(sk) && t && (t->pmtu != pmtu)) {
-               t->pmtu = pmtu;
+               /* Update association pmtu. */
                sctp_assoc_sync_pmtu(asoc);
-               sctp_retransmit(&asoc->outqueue, t, SCTP_RTXR_PMTUD);
        }
+
+       /* Retransmit with the new pmtu setting.
+        * Normally, if PMTU discovery is disabled, an ICMP Fragmentation
+        * Needed will never be sent, but if a message was sent before
+        * PMTU discovery was disabled that was larger than the PMTU, it
+        * would not be fragmented, so it must be re-transmitted fragmented.     
+        */
+       sctp_retransmit(&asoc->outqueue, t, SCTP_RTXR_PMTUD);
+}
+
+/*
+ * SCTP Implementer's Guide, 2.37 ICMP handling procedures
+ *
+ * ICMP8) If the ICMP code is a "Unrecognized next header type encountered"
+ *        or a "Protocol Unreachable" treat this message as an abort
+ *        with the T bit set.
+ *
+ * This function sends an event to the state machine, which will abort the
+ * association.
+ *
+ */
+void sctp_icmp_proto_unreachable(struct sock *sk,
+                           struct sctp_association *asoc,
+                           struct sctp_transport *t)
+{
+       SCTP_DEBUG_PRINTK("%s\n",  __FUNCTION__);
+
+       sctp_do_sm(SCTP_EVENT_T_OTHER,
+                  SCTP_ST_OTHER(SCTP_EVENT_ICMP_PROTO_UNREACH),
+                  asoc->state, asoc->ep, asoc, t,
+                  GFP_ATOMIC);
+
 }
 
 /* Common lookup code for icmp/icmpv6 error handler. */
 struct sock *sctp_err_lookup(int family, struct sk_buff *skb,
                             struct sctphdr *sctphdr,
-                            struct sctp_endpoint **epp,
                             struct sctp_association **app,
                             struct sctp_transport **tpp)
 {
@@ -295,11 +420,10 @@ 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_endpoint *ep = NULL;
        struct sctp_association *asoc = NULL;
        struct sctp_transport *transport = NULL;
 
-       *app = NULL; *epp = NULL; *tpp = NULL;
+       *app = NULL; *tpp = NULL;
 
        af = sctp_get_af_specific(family);
        if (unlikely(!af)) {
@@ -314,25 +438,15 @@ struct sock *sctp_err_lookup(int family, struct sk_buff *skb,
         * packet.
         */
        asoc = __sctp_lookup_association(&saddr, &daddr, &transport);
-       if (!asoc) {
-               /* If there is no matching association, see if it matches any
-                * endpoint. This may happen for an ICMP error generated in
-                * response to an INIT_ACK.
-                */
-               ep = __sctp_rcv_lookup_endpoint(&daddr);
-               if (!ep) {
-                       return NULL;
-               }
-       }
+       if (!asoc)
+               return NULL;
 
-       if (asoc) {
-               if (ntohl(sctphdr->vtag) != asoc->c.peer_vtag) {
-                       ICMP_INC_STATS_BH(ICMP_MIB_INERRORS);
-                       goto out;
-               }
-               sk = asoc->base.sk;
-       } else
-               sk = ep->base.sk;
+       sk = asoc->base.sk;
+
+       if (ntohl(sctphdr->vtag) != asoc->c.peer_vtag) {
+               ICMP_INC_STATS_BH(ICMP_MIB_INERRORS);
+               goto out;
+       }
 
        sctp_bh_lock_sock(sk);
 
@@ -342,7 +456,6 @@ struct sock *sctp_err_lookup(int family, struct sk_buff *skb,
        if (sock_owned_by_user(sk))
                NET_INC_STATS_BH(LINUX_MIB_LOCKDROPPEDICMPS);
 
-       *epp = ep;
        *app = asoc;
        *tpp = transport;
        return sk;
@@ -351,21 +464,16 @@ out:
        sock_put(sk);
        if (asoc)
                sctp_association_put(asoc);
-       if (ep)
-               sctp_endpoint_put(ep);
        return NULL;
 }
 
 /* Common cleanup code for icmp/icmpv6 error handler. */
-void sctp_err_finish(struct sock *sk, struct sctp_endpoint *ep,
-                    struct sctp_association *asoc)
+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);
-       if (ep)
-               sctp_endpoint_put(ep);
 }
 
 /*
@@ -390,10 +498,9 @@ 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_endpoint *ep;
        struct sctp_association *asoc;
        struct sctp_transport *transport;
-       struct inet_opt *inet;
+       struct inet_sock *inet;
        char *saveip, *savesctp;
        int err;
 
@@ -407,7 +514,7 @@ void sctp_v4_err(struct sk_buff *skb, __u32 info)
        savesctp  = skb->h.raw;
        skb->nh.iph = iph;
        skb->h.raw = (char *)sh;
-       sk = sctp_err_lookup(AF_INET, skb, sh, &ep, &asoc, &transport);
+       sk = sctp_err_lookup(AF_INET, skb, sh, &asoc, &transport);
        /* Put back, the original pointers. */
        skb->nh.raw = saveip;
        skb->h.raw = savesctp;
@@ -432,7 +539,13 @@ void sctp_v4_err(struct sk_buff *skb, __u32 info)
                        sctp_icmp_frag_needed(sk, asoc, transport, info);
                        goto out_unlock;
                }
-
+               else {
+                       if (ICMP_PROT_UNREACH == code) {
+                               sctp_icmp_proto_unreachable(sk, asoc,
+                                                           transport);
+                               goto out_unlock;
+                       }
+               }
                err = icmp_err_convert[code].errno;
                break;
        case ICMP_TIME_EXCEEDED:
@@ -457,7 +570,7 @@ void sctp_v4_err(struct sk_buff *skb, __u32 info)
        }
 
 out_unlock:
-       sctp_err_finish(sk, ep, asoc);
+       sctp_err_finish(sk, asoc);
 }
 
 /*
@@ -482,7 +595,13 @@ int sctp_rcv_ootb(struct sk_buff *skb)
 
        /* Scan through all the chunks in the packet.  */
        do {
-               ch_end = ((__u8 *) ch) + WORD_ROUND(ntohs(ch->length));
+               /* Break out if chunk length is less then minimal. */
+               if (ntohs(ch->length) < sizeof(sctp_chunkhdr_t))
+                       break;
+
+               ch_end = ((__u8 *)ch) + WORD_ROUND(ntohs(ch->length));
+               if (ch_end > skb->tail)
+                       break;
 
                /* RFC 8.4, 2) If the OOTB packet contains an ABORT chunk, the
                 * receiver MUST silently discard the OOTB packet and take no
@@ -522,7 +641,7 @@ discard:
 }
 
 /* Insert endpoint into the hash table.  */
-void __sctp_hash_endpoint(struct sctp_endpoint *ep)
+static void __sctp_hash_endpoint(struct sctp_endpoint *ep)
 {
        struct sctp_ep_common **epp;
        struct sctp_ep_common *epb;
@@ -552,7 +671,7 @@ void sctp_hash_endpoint(struct sctp_endpoint *ep)
 }
 
 /* Remove endpoint from the hash table.  */
-void __sctp_unhash_endpoint(struct sctp_endpoint *ep)
+static void __sctp_unhash_endpoint(struct sctp_endpoint *ep)
 {
        struct sctp_hashbucket *head;
        struct sctp_ep_common *epb;
@@ -584,7 +703,7 @@ void sctp_unhash_endpoint(struct sctp_endpoint *ep)
 }
 
 /* Look up an endpoint. */
-struct sctp_endpoint *__sctp_rcv_lookup_endpoint(const union sctp_addr *laddr)
+static struct sctp_endpoint *__sctp_rcv_lookup_endpoint(const union sctp_addr *laddr)
 {
        struct sctp_hashbucket *head;
        struct sctp_ep_common *epb;
@@ -610,16 +729,8 @@ hit:
        return ep;
 }
 
-/* Add an association to the hash. Local BH-safe. */
-void sctp_hash_established(struct sctp_association *asoc)
-{
-       sctp_local_bh_disable();
-       __sctp_hash_established(asoc);
-       sctp_local_bh_enable();
-}
-
 /* Insert association into the hash table.  */
-void __sctp_hash_established(struct sctp_association *asoc)
+static void __sctp_hash_established(struct sctp_association *asoc)
 {
        struct sctp_ep_common **epp;
        struct sctp_ep_common *epb;
@@ -642,16 +753,16 @@ void __sctp_hash_established(struct sctp_association *asoc)
        sctp_write_unlock(&head->lock);
 }
 
-/* Remove association from the hash table.  Local BH-safe. */
-void sctp_unhash_established(struct sctp_association *asoc)
+/* Add an association to the hash. Local BH-safe. */
+void sctp_hash_established(struct sctp_association *asoc)
 {
        sctp_local_bh_disable();
-       __sctp_unhash_established(asoc);
+       __sctp_hash_established(asoc);
        sctp_local_bh_enable();
 }
 
 /* Remove association from the hash table.  */
-void __sctp_unhash_established(struct sctp_association *asoc)
+static void __sctp_unhash_established(struct sctp_association *asoc)
 {
        struct sctp_hashbucket *head;
        struct sctp_ep_common *epb;
@@ -675,8 +786,16 @@ void __sctp_unhash_established(struct sctp_association *asoc)
        sctp_write_unlock(&head->lock);
 }
 
+/* Remove association from the hash table.  Local BH-safe. */
+void sctp_unhash_established(struct sctp_association *asoc)
+{
+       sctp_local_bh_disable();
+       __sctp_unhash_established(asoc);
+       sctp_local_bh_enable();
+}
+
 /* Look up an association. */
-struct sctp_association *__sctp_lookup_association(
+static struct sctp_association *__sctp_lookup_association(
                                        const union sctp_addr *local,
                                        const union sctp_addr *peer,
                                        struct sctp_transport **pt)
@@ -713,8 +832,9 @@ hit:
 }
 
 /* Look up an association. BH-safe. */
+SCTP_STATIC
 struct sctp_association *sctp_lookup_association(const union sctp_addr *laddr,
-                                           const union sctp_addr *paddr,
+                                                const union sctp_addr *paddr,
                                            struct sctp_transport **transportp)
 {
        struct sctp_association *asoc;
@@ -784,6 +904,14 @@ static struct sctp_association *__sctp_rcv_init_lookup(struct sk_buff *skb,
                return NULL;
        }
 
+       /* The code below will attempt to walk the chunk and extract
+        * parameter information.  Before we do that, we need to verify
+        * that the chunk length doesn't cause overflow.  Otherwise, we'll
+        * walk off the end.
+        */
+       if (WORD_ROUND(ntohs(ch->length)) > skb->len)
+               return NULL;
+
        /*
         * This code will NOT touch anything inside the chunk--it is
         * strictly READ-ONLY.
@@ -821,7 +949,7 @@ static struct sctp_association *__sctp_rcv_init_lookup(struct sk_buff *skb,
 }
 
 /* Lookup an association for an inbound skb. */
-struct sctp_association *__sctp_rcv_lookup(struct sk_buff *skb,
+static struct sctp_association *__sctp_rcv_lookup(struct sk_buff *skb,
                                      const union sctp_addr *paddr,
                                      const union sctp_addr *laddr,
                                      struct sctp_transport **transportp)