sctp: Fix SCTP_MAXSEG socket option to comply to spec.
[safe/jmp/linux-2.6] / net / sctp / associola.c
index d5cc731..1f05b94 100644 (file)
@@ -112,6 +112,7 @@ static struct sctp_association *sctp_association_init(struct sctp_association *a
        asoc->cookie_life.tv_usec = (sp->assocparams.sasoc_cookie_life % 1000)
                                        * 1000;
        asoc->frag_point = 0;
+       asoc->user_frag = sp->user_frag;
 
        /* Set the association max_retrans and RTO values from the
         * socket values.
@@ -202,6 +203,7 @@ static struct sctp_association *sctp_association_init(struct sctp_association *a
        asoc->a_rwnd = asoc->rwnd;
 
        asoc->rwnd_over = 0;
+       asoc->rwnd_press = 0;
 
        /* Use my own max window until I learn something better.  */
        asoc->peer.rwnd = SCTP_DEFAULT_MAXWINDOW;
@@ -283,8 +285,7 @@ static struct sctp_association *sctp_association_init(struct sctp_association *a
        if (!sctp_ulpq_init(&asoc->ulpq, asoc))
                goto fail_init;
 
-       /* Set up the tsn tracking. */
-       sctp_tsnmap_init(&asoc->peer.tsn_map, SCTP_TSN_MAP_SIZE, 0);
+       memset(&asoc->peer.tsn_map, 0, sizeof(struct sctp_tsnmap));
 
        asoc->need_ecne = 0;
 
@@ -294,7 +295,8 @@ static struct sctp_association *sctp_association_init(struct sctp_association *a
         * told otherwise.
         */
        asoc->peer.ipv4_address = 1;
-       asoc->peer.ipv6_address = 1;
+       if (asoc->base.sk->sk_family == PF_INET6)
+               asoc->peer.ipv6_address = 1;
        INIT_LIST_HEAD(&asoc->asocs);
 
        asoc->autoclose = sp->autoclose;
@@ -402,6 +404,8 @@ void sctp_association_free(struct sctp_association *asoc)
        /* Dispose of any pending chunks on the inqueue. */
        sctp_inq_free(&asoc->base.inqueue);
 
+       sctp_tsnmap_free(&asoc->peer.tsn_map);
+
        /* Free ssnmap storage. */
        sctp_ssnmap_free(asoc->ssnmap);
 
@@ -464,7 +468,7 @@ static void sctp_association_destroy(struct sctp_association *asoc)
                spin_unlock_bh(&sctp_assocs_id_lock);
        }
 
-       BUG_TRAP(!atomic_read(&asoc->rmem_alloc));
+       WARN_ON(atomic_read(&asoc->rmem_alloc));
 
        if (asoc->base.malloced) {
                kfree(asoc);
@@ -476,6 +480,15 @@ static void sctp_association_destroy(struct sctp_association *asoc)
 void sctp_assoc_set_primary(struct sctp_association *asoc,
                            struct sctp_transport *transport)
 {
+       int changeover = 0;
+
+       /* it's a changeover only if we already have a primary path
+        * that we are changing
+        */
+       if (asoc->peer.primary_path != NULL &&
+           asoc->peer.primary_path != transport)
+               changeover = 1 ;
+
        asoc->peer.primary_path = transport;
 
        /* Set a default msg_name for events. */
@@ -501,12 +514,12 @@ void sctp_assoc_set_primary(struct sctp_association *asoc,
         * double switch to the same destination address.
         */
        if (transport->cacc.changeover_active)
-               transport->cacc.cycling_changeover = 1;
+               transport->cacc.cycling_changeover = changeover;
 
        /* 2) The sender MUST set CHANGEOVER_ACTIVE to indicate that
         * a changeover has occurred.
         */
-       transport->cacc.changeover_active = 1;
+       transport->cacc.changeover_active = changeover;
 
        /* 3) The sender MUST store the next TSN to be sent in
         * next_tsn_at_change.
@@ -556,6 +569,21 @@ void sctp_assoc_rm_peer(struct sctp_association *asoc,
        if (asoc->init_last_sent_to == peer)
                asoc->init_last_sent_to = NULL;
 
+       /* If we remove the transport an SHUTDOWN was last sent to, set it
+        * to NULL. Combined with the update of the retran path above, this
+        * will cause the next SHUTDOWN to be sent to the next available
+        * transport, maintaining the cycle.
+        */
+       if (asoc->shutdown_last_sent_to == peer)
+               asoc->shutdown_last_sent_to = NULL;
+
+       /* If we remove the transport an ASCONF was last sent to, set it to
+        * NULL.
+        */
+       if (asoc->addip_last_asconf &&
+           asoc->addip_last_asconf->transport == peer)
+               asoc->addip_last_asconf->transport = NULL;
+
        asoc->peer.transport_count--;
 
        sctp_transport_free(peer);
@@ -590,11 +618,12 @@ struct sctp_transport *sctp_assoc_add_peer(struct sctp_association *asoc,
        /* Check to see if this is a duplicate. */
        peer = sctp_assoc_lookup_paddr(asoc, addr);
        if (peer) {
+               /* An UNKNOWN state is only set on transports added by
+                * user in sctp_connectx() call.  Such transports should be
+                * considered CONFIRMED per RFC 4960, Section 5.4.
+                */
                if (peer->state == SCTP_UNKNOWN) {
-                       if (peer_state == SCTP_ACTIVE)
-                               peer->state = SCTP_ACTIVE;
-                       if (peer_state == SCTP_UNCONFIRMED)
-                               peer->state = SCTP_UNCONFIRMED;
+                       peer->state = SCTP_ACTIVE;
                }
                return peer;
        }
@@ -644,8 +673,9 @@ struct sctp_transport *sctp_assoc_add_peer(struct sctp_association *asoc,
 
        SCTP_DEBUG_PRINTK("sctp_assoc_add_peer:association %p PMTU set to "
                          "%d\n", asoc, asoc->pathmtu);
+       peer->pmtu_pending = 0;
 
-       asoc->frag_point = sctp_frag_point(sp, asoc->pathmtu);
+       asoc->frag_point = sctp_frag_point(asoc, asoc->pathmtu);
 
        /* The asoc->peer.port might not be meaningful yet, but
         * initialize the packet structure anyway.
@@ -782,11 +812,16 @@ void sctp_assoc_control_transport(struct sctp_association *asoc,
                break;
 
        case SCTP_TRANSPORT_DOWN:
-               /* if the transort was never confirmed, do not transition it
-                * to inactive state.
+               /* If the transport was never confirmed, do not transition it
+                * to inactive state.  Also, release the cached route since
+                * there may be a better route next time.
                 */
                if (transport->state != SCTP_UNCONFIRMED)
                        transport->state = SCTP_INACTIVE;
+               else {
+                       dst_release(transport->dst);
+                       transport->dst = NULL;
+               }
 
                spc_state = SCTP_ADDR_UNREACHABLE;
                break;
@@ -1111,8 +1146,8 @@ void sctp_assoc_update(struct sctp_association *asoc,
        asoc->peer.rwnd = new->peer.rwnd;
        asoc->peer.sack_needed = new->peer.sack_needed;
        asoc->peer.i = new->peer.i;
-       sctp_tsnmap_init(&asoc->peer.tsn_map, SCTP_TSN_MAP_SIZE,
-                        asoc->peer.i.initial_tsn);
+       sctp_tsnmap_init(&asoc->peer.tsn_map, SCTP_TSN_MAP_INITIAL,
+                        asoc->peer.i.initial_tsn, GFP_ATOMIC);
 
        /* Remove any peer addresses not present in the new association. */
        list_for_each_safe(pos, temp, &asoc->peer.transport_addr_list) {
@@ -1256,49 +1291,21 @@ void sctp_assoc_update_retran_path(struct sctp_association *asoc)
                                 ntohs(t->ipaddr.v4.sin_port));
 }
 
-/* Choose the transport for sending a INIT packet.  */
-struct sctp_transport *sctp_assoc_choose_init_transport(
-       struct sctp_association *asoc)
-{
-       struct sctp_transport *t;
-
-       /* Use the retran path. If the last INIT was sent over the
-        * retran path, update the retran path and use it.
-        */
-       if (!asoc->init_last_sent_to) {
-               t = asoc->peer.active_path;
-       } else {
-               if (asoc->init_last_sent_to == asoc->peer.retran_path)
-                       sctp_assoc_update_retran_path(asoc);
-               t = asoc->peer.retran_path;
-       }
-
-       SCTP_DEBUG_PRINTK_IPADDR("sctp_assoc_update_retran_path:association"
-                                " %p addr: ",
-                                " port: %d\n",
-                                asoc,
-                                (&t->ipaddr),
-                                ntohs(t->ipaddr.v4.sin_port));
-
-       return t;
-}
-
-/* Choose the transport for sending a SHUTDOWN packet.  */
-struct sctp_transport *sctp_assoc_choose_shutdown_transport(
-       struct sctp_association *asoc)
+/* Choose the transport for sending retransmit packet.  */
+struct sctp_transport *sctp_assoc_choose_alter_transport(
+       struct sctp_association *asoc, struct sctp_transport *last_sent_to)
 {
-       /* If this is the first time SHUTDOWN is sent, use the active path,
-        * else use the retran path. If the last SHUTDOWN was sent over the
+       /* If this is the first time packet is sent, use the active path,
+        * else use the retran path. If the last packet was sent over the
         * retran path, update the retran path and use it.
         */
-       if (!asoc->shutdown_last_sent_to)
+       if (!last_sent_to)
                return asoc->peer.active_path;
        else {
-               if (asoc->shutdown_last_sent_to == asoc->peer.retran_path)
+               if (last_sent_to == asoc->peer.retran_path)
                        sctp_assoc_update_retran_path(asoc);
                return asoc->peer.retran_path;
        }
-
 }
 
 /* Update the association's pmtu and frag_point by going through all the
@@ -1324,9 +1331,8 @@ void sctp_assoc_sync_pmtu(struct sctp_association *asoc)
        }
 
        if (pmtu) {
-               struct sctp_sock *sp = sctp_sk(asoc->base.sk);
                asoc->pathmtu = pmtu;
-               asoc->frag_point = sctp_frag_point(sp, pmtu);
+               asoc->frag_point = sctp_frag_point(asoc, pmtu);
        }
 
        SCTP_DEBUG_PRINTK("%s: asoc:%p, pmtu:%d, frag_point:%d\n",
@@ -1369,6 +1375,17 @@ void sctp_assoc_rwnd_increase(struct sctp_association *asoc, unsigned len)
                asoc->rwnd += len;
        }
 
+       /* If we had window pressure, start recovering it
+        * once our rwnd had reached the accumulated pressure
+        * threshold.  The idea is to recover slowly, but up
+        * to the initial advertised window.
+        */
+       if (asoc->rwnd_press && asoc->rwnd >= asoc->rwnd_press) {
+               int change = min(asoc->pathmtu, asoc->rwnd_press);
+               asoc->rwnd += change;
+               asoc->rwnd_press -= change;
+       }
+
        SCTP_DEBUG_PRINTK("%s: asoc %p rwnd increased by %d to (%u, %u) "
                          "- %u\n", __func__, asoc, len, asoc->rwnd,
                          asoc->rwnd_over, asoc->a_rwnd);
@@ -1401,17 +1418,38 @@ void sctp_assoc_rwnd_increase(struct sctp_association *asoc, unsigned len)
 /* Decrease asoc's rwnd by len. */
 void sctp_assoc_rwnd_decrease(struct sctp_association *asoc, unsigned len)
 {
+       int rx_count;
+       int over = 0;
+
        SCTP_ASSERT(asoc->rwnd, "rwnd zero", return);
        SCTP_ASSERT(!asoc->rwnd_over, "rwnd_over not zero", return);
+
+       if (asoc->ep->rcvbuf_policy)
+               rx_count = atomic_read(&asoc->rmem_alloc);
+       else
+               rx_count = atomic_read(&asoc->base.sk->sk_rmem_alloc);
+
+       /* If we've reached or overflowed our receive buffer, announce
+        * a 0 rwnd if rwnd would still be positive.  Store the
+        * the pottential pressure overflow so that the window can be restored
+        * back to original value.
+        */
+       if (rx_count >= asoc->base.sk->sk_rcvbuf)
+               over = 1;
+
        if (asoc->rwnd >= len) {
                asoc->rwnd -= len;
+               if (over) {
+                       asoc->rwnd_press = asoc->rwnd;
+                       asoc->rwnd = 0;
+               }
        } else {
                asoc->rwnd_over = len - asoc->rwnd;
                asoc->rwnd = 0;
        }
-       SCTP_DEBUG_PRINTK("%s: asoc %p rwnd decreased by %d to (%u, %u)\n",
+       SCTP_DEBUG_PRINTK("%s: asoc %p rwnd decreased by %d to (%u, %u, %u)\n",
                          __func__, asoc, len, asoc->rwnd,
-                         asoc->rwnd_over);
+                         asoc->rwnd_over, asoc->rwnd_press);
 }
 
 /* Build the bind address list for the association based on info from the
@@ -1470,6 +1508,10 @@ int sctp_assoc_set_id(struct sctp_association *asoc, gfp_t gfp)
 {
        int assoc_id;
        int error = 0;
+
+       /* If the id is already assigned, keep it. */
+       if (asoc->assoc_id)
+               return error;
 retry:
        if (unlikely(!idr_pre_get(&sctp_assocs_id, gfp)))
                return -ENOMEM;