sctp: fix association hangs due to errors when reneging events from the ordering...
authorLee A. Roberts <lee.roberts@hp.com>
Thu, 28 Feb 2013 04:37:29 +0000 (04:37 +0000)
committerDavid S. Miller <davem@davemloft.net>
Thu, 28 Feb 2013 20:34:26 +0000 (15:34 -0500)
In sctp_ulpq_renege_list(), events being reneged from the
ordering queue may correspond to multiple TSNs.  Identify
all affected packets; sum freed space and renege from the
tsnmap.

Signed-off-by: Lee A. Roberts <lee.roberts@hp.com>
Acked-by: Vlad Yasevich <vyasevich@gmail.com>
Acked-by: Neil Horman <nhorman@tuxdriver.com>
net/sctp/ulpqueue.c

index 63afddc..f221fbb 100644 (file)
@@ -962,8 +962,8 @@ static __u16 sctp_ulpq_renege_list(struct sctp_ulpq *ulpq,
                struct sk_buff_head *list, __u16 needed)
 {
        __u16 freed = 0;
-       __u32 tsn;
-       struct sk_buff *skb;
+       __u32 tsn, last_tsn;
+       struct sk_buff *skb, *flist, *last;
        struct sctp_ulpevent *event;
        struct sctp_tsnmap *tsnmap;
 
@@ -977,10 +977,28 @@ static __u16 sctp_ulpq_renege_list(struct sctp_ulpq *ulpq,
                if (TSN_lte(tsn, sctp_tsnmap_get_ctsn(tsnmap)))
                        break;
 
-               __skb_unlink(skb, list);
+               /* Events in ordering queue may have multiple fragments
+                * corresponding to additional TSNs.  Sum the total
+                * freed space; find the last TSN.
+                */
                freed += skb_headlen(skb);
+               flist = skb_shinfo(skb)->frag_list;
+               for (last = flist; flist; flist = flist->next) {
+                       last = flist;
+                       freed += skb_headlen(last);
+               }
+               if (last)
+                       last_tsn = sctp_skb2event(last)->tsn;
+               else
+                       last_tsn = tsn;
+
+               /* Unlink the event, then renege all applicable TSNs. */
+               __skb_unlink(skb, list);
                sctp_ulpevent_free(event);
-               sctp_tsnmap_renege(tsnmap, tsn);
+               while (TSN_lte(tsn, last_tsn)) {
+                       sctp_tsnmap_renege(tsnmap, tsn);
+                       tsn++;
+               }
                if (freed >= needed)
                        return freed;
        }