]> nv-tegra.nvidia Code Review - linux-2.6.git/blobdiff - net/core/skbuff.c
[NET]: Split skb->csum
[linux-2.6.git] / net / core / skbuff.c
index 83fee37de38ee7caa37f102eec58adc383e5a585..a90bc439488e1f0ec07e81d4953f409a9a09d064 100644 (file)
@@ -38,7 +38,6 @@
  *     The functions in this file will not compile correctly with gcc 2.4.x
  */
 
-#include <linux/config.h>
 #include <linux/module.h>
 #include <linux/types.h>
 #include <linux/kernel.h>
@@ -112,6 +111,14 @@ void skb_under_panic(struct sk_buff *skb, int sz, void *here)
        BUG();
 }
 
+void skb_truesize_bug(struct sk_buff *skb)
+{
+       printk(KERN_ERR "SKB BUG: Invalid truesize (%u) "
+              "len=%u, sizeof(sk_buff)=%Zd\n",
+              skb->truesize, skb->len, sizeof(struct sk_buff));
+}
+EXPORT_SYMBOL(skb_truesize_bug);
+
 /*     Allocate a new skbuff. We do this ourselves so we can fill in a few
  *     'private' fields and also do memory statistics to find all the
  *     [BEEP] leaks.
@@ -135,23 +142,22 @@ void skb_under_panic(struct sk_buff *skb, int sz, void *here)
 struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,
                            int fclone)
 {
+       kmem_cache_t *cache;
+       struct skb_shared_info *shinfo;
        struct sk_buff *skb;
        u8 *data;
 
-       /* Get the HEAD */
-       if (fclone)
-               skb = kmem_cache_alloc(skbuff_fclone_cache,
-                                      gfp_mask & ~__GFP_DMA);
-       else
-               skb = kmem_cache_alloc(skbuff_head_cache,
-                                      gfp_mask & ~__GFP_DMA);
+       cache = fclone ? skbuff_fclone_cache : skbuff_head_cache;
 
+       /* Get the HEAD */
+       skb = kmem_cache_alloc(cache, gfp_mask & ~__GFP_DMA);
        if (!skb)
                goto out;
 
        /* Get the DATA. Size must match skb_add_mtu(). */
        size = SKB_DATA_ALIGN(size);
-       data = kmalloc(size + sizeof(struct skb_shared_info), gfp_mask);
+       data = kmalloc_track_caller(size + sizeof(struct skb_shared_info),
+                       gfp_mask);
        if (!data)
                goto nodata;
 
@@ -162,6 +168,16 @@ struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,
        skb->data = data;
        skb->tail = data;
        skb->end  = data + size;
+       /* make sure we initialize shinfo sequentially */
+       shinfo = skb_shinfo(skb);
+       atomic_set(&shinfo->dataref, 1);
+       shinfo->nr_frags  = 0;
+       shinfo->gso_size = 0;
+       shinfo->gso_segs = 0;
+       shinfo->gso_type = 0;
+       shinfo->ip6_frag_id = 0;
+       shinfo->frag_list = NULL;
+
        if (fclone) {
                struct sk_buff *child = skb + 1;
                atomic_t *fclone_ref = (atomic_t *) (child + 1);
@@ -171,17 +187,10 @@ struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,
 
                child->fclone = SKB_FCLONE_UNAVAILABLE;
        }
-       atomic_set(&(skb_shinfo(skb)->dataref), 1);
-       skb_shinfo(skb)->nr_frags  = 0;
-       skb_shinfo(skb)->tso_size = 0;
-       skb_shinfo(skb)->tso_segs = 0;
-       skb_shinfo(skb)->frag_list = NULL;
-       skb_shinfo(skb)->ufo_size = 0;
-       skb_shinfo(skb)->ip6_frag_id = 0;
 out:
        return skb;
 nodata:
-       kmem_cache_free(skbuff_head_cache, skb);
+       kmem_cache_free(cache, skb);
        skb = NULL;
        goto out;
 }
@@ -229,8 +238,9 @@ struct sk_buff *alloc_skb_from_cache(kmem_cache_t *cp,
 
        atomic_set(&(skb_shinfo(skb)->dataref), 1);
        skb_shinfo(skb)->nr_frags  = 0;
-       skb_shinfo(skb)->tso_size = 0;
-       skb_shinfo(skb)->tso_segs = 0;
+       skb_shinfo(skb)->gso_size = 0;
+       skb_shinfo(skb)->gso_segs = 0;
+       skb_shinfo(skb)->gso_type = 0;
        skb_shinfo(skb)->frag_list = NULL;
 out:
        return skb;
@@ -240,12 +250,37 @@ nodata:
        goto out;
 }
 
+/**
+ *     __netdev_alloc_skb - allocate an skbuff for rx on a specific device
+ *     @dev: network device to receive on
+ *     @length: length to allocate
+ *     @gfp_mask: get_free_pages mask, passed to alloc_skb
+ *
+ *     Allocate a new &sk_buff and assign it a usage count of one. The
+ *     buffer has unspecified headroom built in. Users should allocate
+ *     the headroom they think they need without accounting for the
+ *     built in space. The built in space is used for optimisations.
+ *
+ *     %NULL is returned if there is no free memory.
+ */
+struct sk_buff *__netdev_alloc_skb(struct net_device *dev,
+               unsigned int length, gfp_t gfp_mask)
+{
+       struct sk_buff *skb;
+
+       skb = alloc_skb(length + NET_SKB_PAD, gfp_mask);
+       if (likely(skb)) {
+               skb_reserve(skb, NET_SKB_PAD);
+               skb->dev = dev;
+       }
+       return skb;
+}
 
-static void skb_drop_fraglist(struct sk_buff *skb)
+static void skb_drop_list(struct sk_buff **listp)
 {
-       struct sk_buff *list = skb_shinfo(skb)->frag_list;
+       struct sk_buff *list = *listp;
 
-       skb_shinfo(skb)->frag_list = NULL;
+       *listp = NULL;
 
        do {
                struct sk_buff *this = list;
@@ -254,6 +289,11 @@ static void skb_drop_fraglist(struct sk_buff *skb)
        } while (list);
 }
 
+static inline void skb_drop_fraglist(struct sk_buff *skb)
+{
+       skb_drop_list(&skb_shinfo(skb)->frag_list);
+}
+
 static void skb_clone_fraglist(struct sk_buff *skb)
 {
        struct sk_buff *list;
@@ -262,7 +302,7 @@ static void skb_clone_fraglist(struct sk_buff *skb)
                skb_get(list);
 }
 
-void skb_release_data(struct sk_buff *skb)
+static void skb_release_data(struct sk_buff *skb)
 {
        if (!skb->cloned ||
            !atomic_sub_return(skb->nohdr ? (1 << SKB_DATAREF_SHIFT) + 1 : 1,
@@ -354,6 +394,24 @@ void __kfree_skb(struct sk_buff *skb)
        kfree_skbmem(skb);
 }
 
+/**
+ *     kfree_skb - free an sk_buff
+ *     @skb: buffer to free
+ *
+ *     Drop a reference to the buffer and free it if the usage count has
+ *     hit zero.
+ */
+void kfree_skb(struct sk_buff *skb)
+{
+       if (unlikely(!skb))
+               return;
+       if (likely(atomic_read(&skb->users) == 1))
+               smp_rmb();
+       else if (likely(!atomic_dec_and_test(&skb->users)))
+               return;
+       __kfree_skb(skb);
+}
+
 /**
  *     skb_clone       -       duplicate an sk_buff
  *     @skb: buffer to clone
@@ -410,10 +468,13 @@ struct sk_buff *skb_clone(struct sk_buff *skb, gfp_t gfp_mask)
        C(pkt_type);
        C(ip_summed);
        C(priority);
+#if defined(CONFIG_IP_VS) || defined(CONFIG_IP_VS_MODULE)
+       C(ipvs_property);
+#endif
        C(protocol);
        n->destructor = NULL;
+       C(mark);
 #ifdef CONFIG_NETFILTER
-       C(nfmark);
        C(nfct);
        nf_conntrack_get(skb->nfct);
        C(nfctinfo);
@@ -421,13 +482,6 @@ struct sk_buff *skb_clone(struct sk_buff *skb, gfp_t gfp_mask)
        C(nfct_reasm);
        nf_conntrack_get_reasm(skb->nfct_reasm);
 #endif
-#if defined(CONFIG_IP_VS) || defined(CONFIG_IP_VS_MODULE)
-       C(ipvs_property);
-#endif
-#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
-       C(nfct_reasm);
-       nf_conntrack_get_reasm(skb->nfct_reasm);
-#endif
 #ifdef CONFIG_BRIDGE_NETFILTER
        C(nf_bridge);
        nf_bridge_get(skb->nf_bridge);
@@ -441,7 +495,7 @@ struct sk_buff *skb_clone(struct sk_buff *skb, gfp_t gfp_mask)
        n->tc_verd = CLR_TC_MUNGED(n->tc_verd);
        C(input_dev);
 #endif
-
+       skb_copy_secmark(n, skb);
 #endif
        C(truesize);
        atomic_set(&n->users, 1);
@@ -480,8 +534,8 @@ static void copy_skb_header(struct sk_buff *new, const struct sk_buff *old)
        new->pkt_type   = old->pkt_type;
        new->tstamp     = old->tstamp;
        new->destructor = NULL;
+       new->mark       = old->mark;
 #ifdef CONFIG_NETFILTER
-       new->nfmark     = old->nfmark;
        new->nfct       = old->nfct;
        nf_conntrack_get(old->nfct);
        new->nfctinfo   = old->nfctinfo;
@@ -503,9 +557,11 @@ static void copy_skb_header(struct sk_buff *new, const struct sk_buff *old)
 #endif
        new->tc_index   = old->tc_index;
 #endif
+       skb_copy_secmark(new, old);
        atomic_set(&new->users, 1);
-       skb_shinfo(new)->tso_size = skb_shinfo(old)->tso_size;
-       skb_shinfo(new)->tso_segs = skb_shinfo(old)->tso_segs;
+       skb_shinfo(new)->gso_size = skb_shinfo(old)->gso_size;
+       skb_shinfo(new)->gso_segs = skb_shinfo(old)->gso_segs;
+       skb_shinfo(new)->gso_type = skb_shinfo(old)->gso_type;
 }
 
 /**
@@ -583,6 +639,7 @@ struct sk_buff *pskb_copy(struct sk_buff *skb, gfp_t gfp_mask)
        n->csum      = skb->csum;
        n->ip_summed = skb->ip_summed;
 
+       n->truesize += skb->data_len;
        n->data_len  = skb->data_len;
        n->len       = skb->len;
 
@@ -757,70 +814,122 @@ struct sk_buff *skb_copy_expand(const struct sk_buff *skb,
  *     filled. Used by network drivers which may DMA or transfer data
  *     beyond the buffer end onto the wire.
  *
- *     May return NULL in out of memory cases.
+ *     May return error in out of memory cases. The skb is freed on error.
  */
  
-struct sk_buff *skb_pad(struct sk_buff *skb, int pad)
+int skb_pad(struct sk_buff *skb, int pad)
 {
-       struct sk_buff *nskb;
+       int err;
+       int ntail;
        
        /* If the skbuff is non linear tailroom is always zero.. */
-       if (skb_tailroom(skb) >= pad) {
+       if (!skb_cloned(skb) && skb_tailroom(skb) >= pad) {
                memset(skb->data+skb->len, 0, pad);
-               return skb;
+               return 0;
        }
-       
-       nskb = skb_copy_expand(skb, skb_headroom(skb), skb_tailroom(skb) + pad, GFP_ATOMIC);
+
+       ntail = skb->data_len + pad - (skb->end - skb->tail);
+       if (likely(skb_cloned(skb) || ntail > 0)) {
+               err = pskb_expand_head(skb, 0, ntail, GFP_ATOMIC);
+               if (unlikely(err))
+                       goto free_skb;
+       }
+
+       /* FIXME: The use of this function with non-linear skb's really needs
+        * to be audited.
+        */
+       err = skb_linearize(skb);
+       if (unlikely(err))
+               goto free_skb;
+
+       memset(skb->data + skb->len, 0, pad);
+       return 0;
+
+free_skb:
        kfree_skb(skb);
-       if (nskb)
-               memset(nskb->data+nskb->len, 0, pad);
-       return nskb;
+       return err;
 }      
  
-/* Trims skb to length len. It can change skb pointers, if "realloc" is 1.
- * If realloc==0 and trimming is impossible without change of data,
- * it is BUG().
+/* Trims skb to length len. It can change skb pointers.
  */
 
-int ___pskb_trim(struct sk_buff *skb, unsigned int len, int realloc)
+int ___pskb_trim(struct sk_buff *skb, unsigned int len)
 {
+       struct sk_buff **fragp;
+       struct sk_buff *frag;
        int offset = skb_headlen(skb);
        int nfrags = skb_shinfo(skb)->nr_frags;
        int i;
+       int err;
 
-       for (i = 0; i < nfrags; i++) {
+       if (skb_cloned(skb) &&
+           unlikely((err = pskb_expand_head(skb, 0, 0, GFP_ATOMIC))))
+               return err;
+
+       i = 0;
+       if (offset >= len)
+               goto drop_pages;
+
+       for (; i < nfrags; i++) {
                int end = offset + skb_shinfo(skb)->frags[i].size;
-               if (end > len) {
-                       if (skb_cloned(skb)) {
-                               if (!realloc)
-                                       BUG();
-                               if (pskb_expand_head(skb, 0, 0, GFP_ATOMIC))
-                                       return -ENOMEM;
-                       }
-                       if (len <= offset) {
-                               put_page(skb_shinfo(skb)->frags[i].page);
-                               skb_shinfo(skb)->nr_frags--;
-                       } else {
-                               skb_shinfo(skb)->frags[i].size = len - offset;
-                       }
+
+               if (end < len) {
+                       offset = end;
+                       continue;
                }
-               offset = end;
+
+               skb_shinfo(skb)->frags[i++].size = len - offset;
+
+drop_pages:
+               skb_shinfo(skb)->nr_frags = i;
+
+               for (; i < nfrags; i++)
+                       put_page(skb_shinfo(skb)->frags[i].page);
+
+               if (skb_shinfo(skb)->frag_list)
+                       skb_drop_fraglist(skb);
+               goto done;
+       }
+
+       for (fragp = &skb_shinfo(skb)->frag_list; (frag = *fragp);
+            fragp = &frag->next) {
+               int end = offset + frag->len;
+
+               if (skb_shared(frag)) {
+                       struct sk_buff *nfrag;
+
+                       nfrag = skb_clone(frag, GFP_ATOMIC);
+                       if (unlikely(!nfrag))
+                               return -ENOMEM;
+
+                       nfrag->next = frag->next;
+                       kfree_skb(frag);
+                       frag = nfrag;
+                       *fragp = frag;
+               }
+
+               if (end < len) {
+                       offset = end;
+                       continue;
+               }
+
+               if (end > len &&
+                   unlikely((err = pskb_trim(frag, len - offset))))
+                       return err;
+
+               if (frag->next)
+                       skb_drop_list(&frag->next);
+               break;
        }
 
-       if (offset < len) {
+done:
+       if (len > skb_headlen(skb)) {
                skb->data_len -= skb->len - len;
                skb->len       = len;
        } else {
-               if (len <= skb_headlen(skb)) {
-                       skb->len      = len;
-                       skb->data_len = 0;
-                       skb->tail     = skb->data + len;
-                       if (skb_shinfo(skb)->frag_list && !skb_cloned(skb))
-                               skb_drop_fraglist(skb);
-               } else {
-                       skb->data_len -= skb->len - len;
-                       skb->len       = len;
-               }
+               skb->len       = len;
+               skb->data_len  = 0;
+               skb->tail      = skb->data + len;
        }
 
        return 0;
@@ -895,8 +1004,7 @@ unsigned char *__pskb_pull_tail(struct sk_buff *skb, int delta)
                struct sk_buff *insp = NULL;
 
                do {
-                       if (!list)
-                               BUG();
+                       BUG_ON(!list);
 
                        if (list->len <= eat) {
                                /* Eaten as whole. */
@@ -1132,8 +1240,8 @@ EXPORT_SYMBOL(skb_store_bits);
 
 /* Checksum skb data. */
 
-unsigned int skb_checksum(const struct sk_buff *skb, int offset,
-                         int len, unsigned int csum)
+__wsum skb_checksum(const struct sk_buff *skb, int offset,
+                         int len, __wsum csum)
 {
        int start = skb_headlen(skb);
        int i, copy = start - offset;
@@ -1157,7 +1265,7 @@ unsigned int skb_checksum(const struct sk_buff *skb, int offset,
 
                end = start + skb_shinfo(skb)->frags[i].size;
                if ((copy = end - offset) > 0) {
-                       unsigned int csum2;
+                       __wsum csum2;
                        u8 *vaddr;
                        skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
 
@@ -1186,7 +1294,7 @@ unsigned int skb_checksum(const struct sk_buff *skb, int offset,
 
                        end = start + list->len;
                        if ((copy = end - offset) > 0) {
-                               unsigned int csum2;
+                               __wsum csum2;
                                if (copy > len)
                                        copy = len;
                                csum2 = skb_checksum(list, offset - start,
@@ -1200,16 +1308,15 @@ unsigned int skb_checksum(const struct sk_buff *skb, int offset,
                        start = end;
                }
        }
-       if (len)
-               BUG();
+       BUG_ON(len);
 
        return csum;
 }
 
 /* Both of above in one bottle. */
 
-unsigned int skb_copy_and_csum_bits(const struct sk_buff *skb, int offset,
-                                   u8 *to, int len, unsigned int csum)
+__wsum skb_copy_and_csum_bits(const struct sk_buff *skb, int offset,
+                                   u8 *to, int len, __wsum csum)
 {
        int start = skb_headlen(skb);
        int i, copy = start - offset;
@@ -1235,7 +1342,7 @@ unsigned int skb_copy_and_csum_bits(const struct sk_buff *skb, int offset,
 
                end = start + skb_shinfo(skb)->frags[i].size;
                if ((copy = end - offset) > 0) {
-                       unsigned int csum2;
+                       __wsum csum2;
                        u8 *vaddr;
                        skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
 
@@ -1261,7 +1368,7 @@ unsigned int skb_copy_and_csum_bits(const struct sk_buff *skb, int offset,
                struct sk_buff *list = skb_shinfo(skb)->frag_list;
 
                for (; list; list = list->next) {
-                       unsigned int csum2;
+                       __wsum csum2;
                        int end;
 
                        BUG_TRAP(start <= offset + len);
@@ -1283,23 +1390,21 @@ unsigned int skb_copy_and_csum_bits(const struct sk_buff *skb, int offset,
                        start = end;
                }
        }
-       if (len)
-               BUG();
+       BUG_ON(len);
        return csum;
 }
 
 void skb_copy_and_csum_dev(const struct sk_buff *skb, u8 *to)
 {
-       unsigned int csum;
+       __wsum csum;
        long csstart;
 
-       if (skb->ip_summed == CHECKSUM_HW)
+       if (skb->ip_summed == CHECKSUM_PARTIAL)
                csstart = skb->h.raw - skb->data;
        else
                csstart = skb_headlen(skb);
 
-       if (csstart > skb_headlen(skb))
-               BUG();
+       BUG_ON(csstart > skb_headlen(skb));
 
        memcpy(to, skb->data, csstart);
 
@@ -1308,10 +1413,10 @@ void skb_copy_and_csum_dev(const struct sk_buff *skb, u8 *to)
                csum = skb_copy_and_csum_bits(skb, csstart, to + csstart,
                                              skb->len - csstart, 0);
 
-       if (skb->ip_summed == CHECKSUM_HW) {
-               long csstuff = csstart + skb->csum;
+       if (skb->ip_summed == CHECKSUM_PARTIAL) {
+               long csstuff = csstart + skb->csum_offset;
 
-               *((unsigned short *)(to + csstuff)) = csum_fold(csum);
+               *((__sum16 *)(to + csstuff)) = csum_fold(csum);
        }
 }
 
@@ -1705,12 +1810,15 @@ unsigned int skb_find_text(struct sk_buff *skb, unsigned int from,
                           unsigned int to, struct ts_config *config,
                           struct ts_state *state)
 {
+       unsigned int ret;
+
        config->get_next_block = skb_ts_get_next_block;
        config->finish = skb_ts_finish;
 
        skb_prepare_seq_read(skb, from, to, TS_SKB_CB(state));
 
-       return textsearch_find(config, state);
+       ret = textsearch_find(config, state);
+       return (ret <= to - from ? ret : UINT_MAX);
 }
 
 /**
@@ -1785,30 +1893,176 @@ int skb_append_datato_frags(struct sock *sk, struct sk_buff *skb,
        return 0;
 }
 
+/**
+ *     skb_pull_rcsum - pull skb and update receive checksum
+ *     @skb: buffer to update
+ *     @start: start of data before pull
+ *     @len: length of data pulled
+ *
+ *     This function performs an skb_pull on the packet and updates
+ *     update the CHECKSUM_COMPLETE checksum.  It should be used on
+ *     receive path processing instead of skb_pull unless you know
+ *     that the checksum difference is zero (e.g., a valid IP header)
+ *     or you are setting ip_summed to CHECKSUM_NONE.
+ */
+unsigned char *skb_pull_rcsum(struct sk_buff *skb, unsigned int len)
+{
+       BUG_ON(len > skb->len);
+       skb->len -= len;
+       BUG_ON(skb->len < skb->data_len);
+       skb_postpull_rcsum(skb, skb->data, len);
+       return skb->data += len;
+}
+
+EXPORT_SYMBOL_GPL(skb_pull_rcsum);
+
+/**
+ *     skb_segment - Perform protocol segmentation on skb.
+ *     @skb: buffer to segment
+ *     @features: features for the output path (see dev->features)
+ *
+ *     This function performs segmentation on the given skb.  It returns
+ *     the segment at the given position.  It returns NULL if there are
+ *     no more segments to generate, or when an error is encountered.
+ */
+struct sk_buff *skb_segment(struct sk_buff *skb, int features)
+{
+       struct sk_buff *segs = NULL;
+       struct sk_buff *tail = NULL;
+       unsigned int mss = skb_shinfo(skb)->gso_size;
+       unsigned int doffset = skb->data - skb->mac.raw;
+       unsigned int offset = doffset;
+       unsigned int headroom;
+       unsigned int len;
+       int sg = features & NETIF_F_SG;
+       int nfrags = skb_shinfo(skb)->nr_frags;
+       int err = -ENOMEM;
+       int i = 0;
+       int pos;
+
+       __skb_push(skb, doffset);
+       headroom = skb_headroom(skb);
+       pos = skb_headlen(skb);
+
+       do {
+               struct sk_buff *nskb;
+               skb_frag_t *frag;
+               int hsize;
+               int k;
+               int size;
+
+               len = skb->len - offset;
+               if (len > mss)
+                       len = mss;
+
+               hsize = skb_headlen(skb) - offset;
+               if (hsize < 0)
+                       hsize = 0;
+               if (hsize > len || !sg)
+                       hsize = len;
+
+               nskb = alloc_skb(hsize + doffset + headroom, GFP_ATOMIC);
+               if (unlikely(!nskb))
+                       goto err;
+
+               if (segs)
+                       tail->next = nskb;
+               else
+                       segs = nskb;
+               tail = nskb;
+
+               nskb->dev = skb->dev;
+               nskb->priority = skb->priority;
+               nskb->protocol = skb->protocol;
+               nskb->dst = dst_clone(skb->dst);
+               memcpy(nskb->cb, skb->cb, sizeof(skb->cb));
+               nskb->pkt_type = skb->pkt_type;
+               nskb->mac_len = skb->mac_len;
+
+               skb_reserve(nskb, headroom);
+               nskb->mac.raw = nskb->data;
+               nskb->nh.raw = nskb->data + skb->mac_len;
+               nskb->h.raw = nskb->nh.raw + (skb->h.raw - skb->nh.raw);
+               memcpy(skb_put(nskb, doffset), skb->data, doffset);
+
+               if (!sg) {
+                       nskb->csum = skb_copy_and_csum_bits(skb, offset,
+                                                           skb_put(nskb, len),
+                                                           len, 0);
+                       continue;
+               }
+
+               frag = skb_shinfo(nskb)->frags;
+               k = 0;
+
+               nskb->ip_summed = CHECKSUM_PARTIAL;
+               nskb->csum = skb->csum;
+               memcpy(skb_put(nskb, hsize), skb->data + offset, hsize);
+
+               while (pos < offset + len) {
+                       BUG_ON(i >= nfrags);
+
+                       *frag = skb_shinfo(skb)->frags[i];
+                       get_page(frag->page);
+                       size = frag->size;
+
+                       if (pos < offset) {
+                               frag->page_offset += offset - pos;
+                               frag->size -= offset - pos;
+                       }
+
+                       k++;
+
+                       if (pos + size <= offset + len) {
+                               i++;
+                               pos += size;
+                       } else {
+                               frag->size -= pos + size - (offset + len);
+                               break;
+                       }
+
+                       frag++;
+               }
+
+               skb_shinfo(nskb)->nr_frags = k;
+               nskb->data_len = len - hsize;
+               nskb->len += nskb->data_len;
+               nskb->truesize += nskb->data_len;
+       } while ((offset += len) < skb->len);
+
+       return segs;
+
+err:
+       while ((skb = segs)) {
+               segs = skb->next;
+               kfree(skb);
+       }
+       return ERR_PTR(err);
+}
+
+EXPORT_SYMBOL_GPL(skb_segment);
+
 void __init skb_init(void)
 {
        skbuff_head_cache = kmem_cache_create("skbuff_head_cache",
                                              sizeof(struct sk_buff),
                                              0,
-                                             SLAB_HWCACHE_ALIGN,
+                                             SLAB_HWCACHE_ALIGN|SLAB_PANIC,
                                              NULL, NULL);
-       if (!skbuff_head_cache)
-               panic("cannot create skbuff cache");
-
        skbuff_fclone_cache = kmem_cache_create("skbuff_fclone_cache",
                                                (2*sizeof(struct sk_buff)) +
                                                sizeof(atomic_t),
                                                0,
-                                               SLAB_HWCACHE_ALIGN,
+                                               SLAB_HWCACHE_ALIGN|SLAB_PANIC,
                                                NULL, NULL);
-       if (!skbuff_fclone_cache)
-               panic("cannot create skbuff cache");
 }
 
 EXPORT_SYMBOL(___pskb_trim);
 EXPORT_SYMBOL(__kfree_skb);
+EXPORT_SYMBOL(kfree_skb);
 EXPORT_SYMBOL(__pskb_pull_tail);
 EXPORT_SYMBOL(__alloc_skb);
+EXPORT_SYMBOL(__netdev_alloc_skb);
 EXPORT_SYMBOL(pskb_copy);
 EXPORT_SYMBOL(pskb_expand_head);
 EXPORT_SYMBOL(skb_checksum);