unsigned long   mibs[SCTP_MIB_MAX];
 };
 
+/* helper function to track stats about max rto and related transport */
+static inline void sctp_max_rto(struct sctp_association *asoc,
+                               struct sctp_transport *trans)
+{
+       if (asoc->stats.max_obs_rto < (__u64)trans->rto) {
+               asoc->stats.max_obs_rto = trans->rto;
+               memset(&asoc->stats.obs_rto_ipaddr, 0,
+                       sizeof(struct sockaddr_storage));
+               memcpy(&asoc->stats.obs_rto_ipaddr, &trans->ipaddr,
+                       trans->af_specific->sockaddr_len);
+       }
+}
 
 /* Print debugging messages.  */
 #if SCTP_DEBUG
 
        __u32 initial_tsn;
 };
 
+/* SCTP_GET_ASSOC_STATS counters */
+struct sctp_priv_assoc_stats {
+       /* Maximum observed rto in the association during subsequent
+        * observations. Value is set to 0 if no RTO measurement took place
+        * The transport where the max_rto was observed is returned in
+        * obs_rto_ipaddr
+        */
+       struct sockaddr_storage obs_rto_ipaddr;
+       __u64 max_obs_rto;
+       /* Total In and Out SACKs received and sent */
+       __u64 isacks;
+       __u64 osacks;
+       /* Total In and Out packets received and sent */
+       __u64 opackets;
+       __u64 ipackets;
+       /* Total retransmitted chunks */
+       __u64 rtxchunks;
+       /* TSN received > next expected */
+       __u64 outofseqtsns;
+       /* Duplicate Chunks received */
+       __u64 idupchunks;
+       /* Gap Ack Blocks received */
+       __u64 gapcnt;
+       /* Unordered data chunks sent and received */
+       __u64 ouodchunks;
+       __u64 iuodchunks;
+       /* Ordered data chunks sent and received */
+       __u64 oodchunks;
+       __u64 iodchunks;
+       /* Control chunks sent and received */
+       __u64 octrlchunks;
+       __u64 ictrlchunks;
+};
+
 /* RFC2960
  *
  * 12. Recommended Transmission Control Block (TCB) Parameters
 
        __u8 need_ecne:1,       /* Need to send an ECNE Chunk? */
             temp:1;            /* Is it a temporary association? */
+
+       struct sctp_priv_assoc_stats stats;
 };
 
 
 
 #define SCTP_GET_LOCAL_ADDRS   109             /* Get all local address. */
 #define SCTP_SOCKOPT_CONNECTX  110             /* CONNECTX requests. */
 #define SCTP_SOCKOPT_CONNECTX3 111     /* CONNECTX requests (updated) */
+#define SCTP_GET_ASSOC_STATS   112     /* Read only */
 
 /*
  * 5.2.1 SCTP Initiation Structure (SCTP_INIT)
        __u8                    addrs[0]; /*output, variable size*/
 };
 
+/* A socket user request obtained via SCTP_GET_ASSOC_STATS that retrieves
+ * association stats. All stats are counts except sas_maxrto and
+ * sas_obs_rto_ipaddr. maxrto is the max observed rto + transport since
+ * the last call. Will return 0 when RTO was not update since last call
+ */
+struct sctp_assoc_stats {
+       sctp_assoc_t    sas_assoc_id;    /* Input */
+                                        /* Transport of observed max RTO */
+       struct sockaddr_storage sas_obs_rto_ipaddr;
+       __u64           sas_maxrto;      /* Maximum Observed RTO for period */
+       __u64           sas_isacks;      /* SACKs received */
+       __u64           sas_osacks;      /* SACKs sent */
+       __u64           sas_opackets;    /* Packets sent */
+       __u64           sas_ipackets;    /* Packets received */
+       __u64           sas_rtxchunks;   /* Retransmitted Chunks */
+       __u64           sas_outofseqtsns;/* TSN received > next expected */
+       __u64           sas_idupchunks;  /* Dups received (ordered+unordered) */
+       __u64           sas_gapcnt;      /* Gap Acknowledgements Received */
+       __u64           sas_ouodchunks;  /* Unordered data chunks sent */
+       __u64           sas_iuodchunks;  /* Unordered data chunks received */
+       __u64           sas_oodchunks;   /* Ordered data chunks sent */
+       __u64           sas_iodchunks;   /* Ordered data chunks received */
+       __u64           sas_octrlchunks; /* Control chunks sent */
+       __u64           sas_ictrlchunks; /* Control chunks received */
+};
+
 /* These are bit fields for msghdr->msg_flags.  See section 5.1.  */
 /* On user space Linux, these live in <bits/socket.h> as an enum.  */
 enum sctp_msg_flags {
 
        asoc->default_timetolive = sp->default_timetolive;
        asoc->default_rcv_context = sp->default_rcv_context;
 
+       /* SCTP_GET_ASSOC_STATS COUNTERS */
+       memset(&asoc->stats, 0, sizeof(struct sctp_priv_assoc_stats));
+
        /* AUTH related initializations */
        INIT_LIST_HEAD(&asoc->endpoint_shared_keys);
        err = sctp_auth_asoc_copy_shkeys(ep, asoc, gfp);
 
        /* Set the transport's RTO.initial value */
        peer->rto = asoc->rto_initial;
+       sctp_max_rto(asoc, peer);
 
        /* Set the peer's active state. */
        peer->state = peer_state;
                 */
                if (sctp_chunk_is_data(chunk))
                        asoc->peer.last_data_from = chunk->transport;
-               else
+               else {
                        SCTP_INC_STATS(net, SCTP_MIB_INCTRLCHUNKS);
+                       asoc->stats.ictrlchunks++;
+                       if (chunk->chunk_hdr->type == SCTP_CID_SACK)
+                               asoc->stats.isacks++;
+               }
 
                if (chunk->transport)
                        chunk->transport->last_time_heard = jiffies;
 
                 */
                if (asoc && sctp_chunk_is_data(chunk))
                        asoc->peer.last_data_from = chunk->transport;
-               else
+               else {
                        SCTP_INC_STATS(sock_net(ep->base.sk), SCTP_MIB_INCTRLCHUNKS);
+                       if (asoc)
+                               asoc->stats.ictrlchunks++;
+               }
 
                if (chunk->transport)
                        chunk->transport->last_time_heard = jiffies;
 
         * on the BH related data structures.
         */
        list_add_tail(&chunk->list, &q->in_chunk_list);
+       if (chunk->asoc)
+               chunk->asoc->stats.ipackets++;
        q->immediate.func(&q->immediate);
 }
 
 
 
            case SCTP_CID_SACK:
                packet->has_sack = 1;
+               if (chunk->asoc)
+                       chunk->asoc->stats.osacks++;
                break;
 
            case SCTP_CID_AUTH:
         */
 
        /* Dump that on IP!  */
-       if (asoc && asoc->peer.last_sent_to != tp) {
-               /* Considering the multiple CPU scenario, this is a
-                * "correcter" place for last_sent_to.  --xguo
-                */
-               asoc->peer.last_sent_to = tp;
+       if (asoc) {
+               asoc->stats.opackets++;
+               if (asoc->peer.last_sent_to != tp)
+                       /* Considering the multiple CPU scenario, this is a
+                        * "correcter" place for last_sent_to.  --xguo
+                        */
+                       asoc->peer.last_sent_to = tp;
        }
 
        if (has_data) {
 
                                chunk->fast_retransmit = SCTP_DONT_FRTX;
 
                        q->empty = 0;
+                       q->asoc->stats.rtxchunks++;
                        break;
                }
 
                        if (status  != SCTP_XMIT_OK) {
                                /* put the chunk back */
                                list_add(&chunk->list, &q->control_chunk_list);
-                       } else if (chunk->chunk_hdr->type == SCTP_CID_FWD_TSN) {
+                       } else {
+                               asoc->stats.octrlchunks++;
                                /* PR-SCTP C5) If a FORWARD TSN is sent, the
                                 * sender MUST assure that at least one T3-rtx
                                 * timer is running.
                                 */
-                               sctp_transport_reset_timers(transport);
+                               if (chunk->chunk_hdr->type == SCTP_CID_FWD_TSN)
+                                       sctp_transport_reset_timers(transport);
                        }
                        break;
 
                                 */
                                if (asoc->state == SCTP_STATE_SHUTDOWN_PENDING)
                                        chunk->chunk_hdr->flags |= SCTP_DATA_SACK_IMM;
+                               if (chunk->chunk_hdr->flags & SCTP_DATA_UNORDERED)
+                                       asoc->stats.ouodchunks++;
+                               else
+                                       asoc->stats.oodchunks++;
 
                                break;
 
 
        sack_ctsn = ntohl(sack->cum_tsn_ack);
        gap_ack_blocks = ntohs(sack->num_gap_ack_blocks);
+       asoc->stats.gapcnt += gap_ack_blocks;
        /*
         * SFR-CACC algorithm:
         * On receipt of a SACK the sender SHOULD execute the
 
                                 gabs);
 
        /* Add the duplicate TSN information.  */
-       if (num_dup_tsns)
+       if (num_dup_tsns) {
+               aptr->stats.idupchunks += num_dup_tsns;
                sctp_addto_chunk(retval, sizeof(__u32) * num_dup_tsns,
                                 sctp_tsnmap_get_dups(map));
-
+       }
        /* Once we have a sack generated, check to see what our sack
         * generation is, if its 0, reset the transports to 0, and reset
         * the association generation to 1
 
         */
        if (!is_hb || transport->hb_sent) {
                transport->rto = min((transport->rto * 2), transport->asoc->rto_max);
+               sctp_max_rto(asoc, transport);
        }
 }
 
 
                /* The TSN is too high--silently discard the chunk and
                 * count on it getting retransmitted later.
                 */
+               if (chunk->asoc)
+                       chunk->asoc->stats.outofseqtsns++;
                return SCTP_IERROR_HIGH_TSN;
        } else if (tmp > 0) {
                /* This is a duplicate.  Record it.  */
        /* Note: Some chunks may get overcounted (if we drop) or overcounted
         * if we renege and the chunk arrives again.
         */
-       if (chunk->chunk_hdr->flags & SCTP_DATA_UNORDERED)
+       if (chunk->chunk_hdr->flags & SCTP_DATA_UNORDERED) {
                SCTP_INC_STATS(net, SCTP_MIB_INUNORDERCHUNKS);
-       else {
+               if (chunk->asoc)
+                       chunk->asoc->stats.iuodchunks++;
+       } else {
                SCTP_INC_STATS(net, SCTP_MIB_INORDERCHUNKS);
+               if (chunk->asoc)
+                       chunk->asoc->stats.iodchunks++;
                ordered = 1;
        }
 
 
                                    2*asoc->pathmtu, 4380));
                                trans->ssthresh = asoc->peer.i.a_rwnd;
                                trans->rto = asoc->rto_initial;
+                               sctp_max_rto(asoc, trans);
                                trans->rtt = trans->srtt = trans->rttvar = 0;
                                sctp_transport_route(trans, NULL,
                                    sctp_sk(asoc->base.sk));
        return 0;
 }
 
+/*
+ * SCTP_GET_ASSOC_STATS
+ *
+ * This option retrieves local per endpoint statistics. It is modeled
+ * after OpenSolaris' implementation
+ */
+static int sctp_getsockopt_assoc_stats(struct sock *sk, int len,
+                                      char __user *optval,
+                                      int __user *optlen)
+{
+       struct sctp_assoc_stats sas;
+       struct sctp_association *asoc = NULL;
+
+       /* User must provide at least the assoc id */
+       if (len < sizeof(sctp_assoc_t))
+               return -EINVAL;
+
+       if (copy_from_user(&sas, optval, len))
+               return -EFAULT;
+
+       asoc = sctp_id2assoc(sk, sas.sas_assoc_id);
+       if (!asoc)
+               return -EINVAL;
+
+       sas.sas_rtxchunks = asoc->stats.rtxchunks;
+       sas.sas_gapcnt = asoc->stats.gapcnt;
+       sas.sas_outofseqtsns = asoc->stats.outofseqtsns;
+       sas.sas_osacks = asoc->stats.osacks;
+       sas.sas_isacks = asoc->stats.isacks;
+       sas.sas_octrlchunks = asoc->stats.octrlchunks;
+       sas.sas_ictrlchunks = asoc->stats.ictrlchunks;
+       sas.sas_oodchunks = asoc->stats.oodchunks;
+       sas.sas_iodchunks = asoc->stats.iodchunks;
+       sas.sas_ouodchunks = asoc->stats.ouodchunks;
+       sas.sas_iuodchunks = asoc->stats.iuodchunks;
+       sas.sas_idupchunks = asoc->stats.idupchunks;
+       sas.sas_opackets = asoc->stats.opackets;
+       sas.sas_ipackets = asoc->stats.ipackets;
+
+       /* New high max rto observed, will return 0 if not a single
+        * RTO update took place. obs_rto_ipaddr will be bogus
+        * in such a case
+        */
+       sas.sas_maxrto = asoc->stats.max_obs_rto;
+       memcpy(&sas.sas_obs_rto_ipaddr, &asoc->stats.obs_rto_ipaddr,
+               sizeof(struct sockaddr_storage));
+
+       /* Mark beginning of a new observation period */
+       asoc->stats.max_obs_rto = asoc->rto_min;
+
+       /* Allow the struct to grow and fill in as much as possible */
+       len = min_t(size_t, len, sizeof(sas));
+
+       if (put_user(len, optlen))
+               return -EFAULT;
+
+       SCTP_DEBUG_PRINTK("sctp_getsockopt_assoc_stat(%d): %d\n",
+                         len, sas.sas_assoc_id);
+
+       if (copy_to_user(optval, &sas, len))
+               return -EFAULT;
+
+       return 0;
+}
+
 SCTP_STATIC int sctp_getsockopt(struct sock *sk, int level, int optname,
                                char __user *optval, int __user *optlen)
 {
        case SCTP_PEER_ADDR_THLDS:
                retval = sctp_getsockopt_paddr_thresholds(sk, optval, len, optlen);
                break;
+       case SCTP_GET_ASSOC_STATS:
+               retval = sctp_getsockopt_assoc_stats(sk, len, optval, optlen);
+               break;
        default:
                retval = -ENOPROTOOPT;
                break;
 
        if (tp->rto > tp->asoc->rto_max)
                tp->rto = tp->asoc->rto_max;
 
+       sctp_max_rto(tp->asoc, tp);
        tp->rtt = rtt;
 
        /* Reset rto_pending so that a new RTT measurement is started when a
        t->burst_limited = 0;
        t->ssthresh = asoc->peer.i.a_rwnd;
        t->rto = asoc->rto_initial;
+       sctp_max_rto(asoc, t);
        t->rtt = 0;
        t->srtt = 0;
        t->rttvar = 0;