net: Add frag_list support to skb_segment
authorHerbert Xu <herbert@gondor.apana.org.au>
Tue, 16 Dec 2008 07:26:06 +0000 (23:26 -0800)
committerDavid S. Miller <davem@davemloft.net>
Tue, 16 Dec 2008 07:26:06 +0000 (23:26 -0800)
This patch adds limited support for handling frag_list packets in
skb_segment.  The intention is to support GRO (Generic Receive Offload)
packets which will be constructed by chaining normal packets using
frag_list.

As such we require all frag_list members terminate on exact MSS
boundaries.  This is checked using BUG_ON.

As there should only be one producer in the kernel of such packets,
namely GRO, this requirement should not be difficult to maintain.

Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
net/core/skbuff.c

index b1f628741f4c45bd911dab8c2520710e97105ba2..18e224af05a632a213fdd19634dc290f01491646 100644 (file)
@@ -2428,6 +2428,7 @@ struct sk_buff *skb_segment(struct sk_buff *skb, int features)
 {
        struct sk_buff *segs = NULL;
        struct sk_buff *tail = NULL;
+       struct sk_buff *fskb = skb_shinfo(skb)->frag_list;
        unsigned int mss = skb_shinfo(skb)->gso_size;
        unsigned int doffset = skb->data - skb_mac_header(skb);
        unsigned int offset = doffset;
@@ -2447,7 +2448,6 @@ struct sk_buff *skb_segment(struct sk_buff *skb, int features)
                struct sk_buff *nskb;
                skb_frag_t *frag;
                int hsize;
-               int k;
                int size;
 
                len = skb->len - offset;
@@ -2460,9 +2460,36 @@ struct sk_buff *skb_segment(struct sk_buff *skb, int features)
                if (hsize > len || !sg)
                        hsize = len;
 
-               nskb = alloc_skb(hsize + doffset + headroom, GFP_ATOMIC);
-               if (unlikely(!nskb))
-                       goto err;
+               if (!hsize && i >= nfrags) {
+                       BUG_ON(fskb->len != len);
+
+                       pos += len;
+                       nskb = skb_clone(fskb, GFP_ATOMIC);
+                       fskb = fskb->next;
+
+                       if (unlikely(!nskb))
+                               goto err;
+
+                       hsize = skb_end_pointer(nskb) - nskb->head;
+                       if (skb_cow_head(nskb, doffset + headroom)) {
+                               kfree_skb(nskb);
+                               goto err;
+                       }
+
+                       nskb->truesize += skb_end_pointer(nskb) - nskb->head -
+                                         hsize;
+                       skb_release_head_state(nskb);
+                       __skb_push(nskb, doffset);
+               } else {
+                       nskb = alloc_skb(hsize + doffset + headroom,
+                                        GFP_ATOMIC);
+
+                       if (unlikely(!nskb))
+                               goto err;
+
+                       skb_reserve(nskb, headroom);
+                       __skb_put(nskb, doffset);
+               }
 
                if (segs)
                        tail->next = nskb;
@@ -2473,13 +2500,15 @@ struct sk_buff *skb_segment(struct sk_buff *skb, int features)
                __copy_skb_header(nskb, skb);
                nskb->mac_len = skb->mac_len;
 
-               skb_reserve(nskb, headroom);
                skb_reset_mac_header(nskb);
                skb_set_network_header(nskb, skb->mac_len);
                nskb->transport_header = (nskb->network_header +
                                          skb_network_header_len(skb));
-               skb_copy_from_linear_data(skb, skb_put(nskb, doffset),
-                                         doffset);
+               skb_copy_from_linear_data(skb, nskb->data, doffset);
+
+               if (pos >= offset + len)
+                       continue;
+
                if (!sg) {
                        nskb->ip_summed = CHECKSUM_NONE;
                        nskb->csum = skb_copy_and_csum_bits(skb, offset,
@@ -2489,14 +2518,11 @@ struct sk_buff *skb_segment(struct sk_buff *skb, int features)
                }
 
                frag = skb_shinfo(nskb)->frags;
-               k = 0;
 
                skb_copy_from_linear_data_offset(skb, offset,
                                                 skb_put(nskb, hsize), hsize);
 
-               while (pos < offset + len) {
-                       BUG_ON(i >= nfrags);
-
+               while (pos < offset + len && i < nfrags) {
                        *frag = skb_shinfo(skb)->frags[i];
                        get_page(frag->page);
                        size = frag->size;
@@ -2506,20 +2532,39 @@ struct sk_buff *skb_segment(struct sk_buff *skb, int features)
                                frag->size -= offset - pos;
                        }
 
-                       k++;
+                       skb_shinfo(nskb)->nr_frags++;
 
                        if (pos + size <= offset + len) {
                                i++;
                                pos += size;
                        } else {
                                frag->size -= pos + size - (offset + len);
-                               break;
+                               goto skip_fraglist;
                        }
 
                        frag++;
                }
 
-               skb_shinfo(nskb)->nr_frags = k;
+               if (pos < offset + len) {
+                       struct sk_buff *fskb2 = fskb;
+
+                       BUG_ON(pos + fskb->len != offset + len);
+
+                       pos += fskb->len;
+                       fskb = fskb->next;
+
+                       if (fskb2->next) {
+                               fskb2 = skb_clone(fskb2, GFP_ATOMIC);
+                               if (!fskb2)
+                                       goto err;
+                       } else
+                               skb_get(fskb2);
+
+                       BUG_ON(skb_shinfo(nskb)->frag_list);
+                       skb_shinfo(nskb)->frag_list = fskb2;
+               }
+
+skip_fraglist:
                nskb->data_len = len - hsize;
                nskb->len += nskb->data_len;
                nskb->truesize += nskb->data_len;