sctp: Fix SCTP_MAXSEG socket option to comply to spec.
[safe/jmp/linux-2.6] / net / sctp / associola.c
index 39f5166..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;
@@ -673,7 +675,7 @@ struct sctp_transport *sctp_assoc_add_peer(struct sctp_association *asoc,
                          "%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.
@@ -810,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;
@@ -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;