sctp: allow GSO frags to access the chunk too
[cascardo/linux.git] / net / sctp / inqueue.c
index 9d87bba..147d975 100644 (file)
@@ -130,13 +130,25 @@ struct sctp_chunk *sctp_inq_pop(struct sctp_inq *queue)
         * at this time.
         */
 
-       if ((chunk = queue->in_progress)) {
+       chunk = queue->in_progress;
+       if (chunk) {
                /* There is a packet that we have been working on.
                 * Any post processing work to do before we move on?
                 */
                if (chunk->singleton ||
                    chunk->end_of_packet ||
                    chunk->pdiscard) {
+                       if (chunk->head_skb == chunk->skb) {
+                               chunk->skb = skb_shinfo(chunk->skb)->frag_list;
+                               goto new_skb;
+                       }
+                       if (chunk->skb->next) {
+                               chunk->skb = chunk->skb->next;
+                               goto new_skb;
+                       }
+
+                       if (chunk->head_skb)
+                               chunk->skb = chunk->head_skb;
                        sctp_chunk_free(chunk);
                        chunk = queue->in_progress = NULL;
                } else {
@@ -152,34 +164,71 @@ struct sctp_chunk *sctp_inq_pop(struct sctp_inq *queue)
        if (!chunk) {
                struct list_head *entry;
 
+next_chunk:
                /* Is the queue empty?  */
-               if (list_empty(&queue->in_chunk_list))
+               entry = sctp_list_dequeue(&queue->in_chunk_list);
+               if (!entry)
                        return NULL;
 
-               entry = queue->in_chunk_list.next;
-               chunk = queue->in_progress =
-                       list_entry(entry, struct sctp_chunk, list);
-               list_del_init(entry);
+               chunk = list_entry(entry, struct sctp_chunk, list);
+
+               /* Linearize if it's not GSO */
+               if ((skb_shinfo(chunk->skb)->gso_type & SKB_GSO_SCTP) != SKB_GSO_SCTP &&
+                   skb_is_nonlinear(chunk->skb)) {
+                       if (skb_linearize(chunk->skb)) {
+                               __SCTP_INC_STATS(dev_net(chunk->skb->dev), SCTP_MIB_IN_PKT_DISCARDS);
+                               sctp_chunk_free(chunk);
+                               goto next_chunk;
+                       }
+
+                       /* Update sctp_hdr as it probably changed */
+                       chunk->sctp_hdr = sctp_hdr(chunk->skb);
+               }
+
+               if ((skb_shinfo(chunk->skb)->gso_type & SKB_GSO_SCTP) == SKB_GSO_SCTP) {
+                       /* GSO-marked skbs but without frags, handle
+                        * them normally
+                        */
+                       if (skb_shinfo(chunk->skb)->frag_list)
+                               chunk->head_skb = chunk->skb;
+
+                       /* skbs with "cover letter" */
+                       if (chunk->head_skb && chunk->skb->data_len == chunk->skb->len)
+                               chunk->skb = skb_shinfo(chunk->skb)->frag_list;
+
+                       if (WARN_ON(!chunk->skb)) {
+                               __SCTP_INC_STATS(dev_net(chunk->skb->dev), SCTP_MIB_IN_PKT_DISCARDS);
+                               sctp_chunk_free(chunk);
+                               goto next_chunk;
+                       }
+               }
+
+               if (chunk->asoc)
+                       sock_rps_save_rxhash(chunk->asoc->base.sk, chunk->skb);
+
+               queue->in_progress = chunk;
 
+new_skb:
                /* This is the first chunk in the packet.  */
-               chunk->singleton = 1;
                ch = (sctp_chunkhdr_t *) chunk->skb->data;
+               chunk->singleton = 1;
                chunk->data_accepted = 0;
+               chunk->pdiscard = 0;
+               chunk->auth = 0;
+               chunk->has_asconf = 0;
+               chunk->end_of_packet = 0;
+               chunk->ecn_ce_done = 0;
+               if (chunk->head_skb) {
+                       struct sctp_input_cb
+                               *cb = SCTP_INPUT_CB(chunk->skb),
+                               *head_cb = SCTP_INPUT_CB(chunk->head_skb);
 
-               if (chunk->asoc)
-                       sock_rps_save_rxhash(chunk->asoc->base.sk, chunk->skb);
+                       cb->chunk = head_cb->chunk;
+               }
        }
 
        chunk->chunk_hdr = ch;
        chunk->chunk_end = ((__u8 *)ch) + WORD_ROUND(ntohs(ch->length));
-       /* In the unlikely case of an IP reassembly, the skb could be
-        * non-linear. If so, update chunk_end so that it doesn't go past
-        * the skb->tail.
-        */
-       if (unlikely(skb_is_nonlinear(chunk->skb))) {
-               if (chunk->chunk_end > skb_tail_pointer(chunk->skb))
-                       chunk->chunk_end = skb_tail_pointer(chunk->skb);
-       }
        skb_pull(chunk->skb, sizeof(sctp_chunkhdr_t));
        chunk->subh.v = NULL; /* Subheader is no longer valid.  */