|
@@ -1842,6 +1842,132 @@ unsigned char *skb_pull_rcsum(struct sk_buff *skb, unsigned int len)
|
|
|
|
|
|
EXPORT_SYMBOL_GPL(skb_pull_rcsum);
|
|
|
|
|
|
+/**
|
|
|
+ * skb_segment - Perform protocol segmentation on skb.
|
|
|
+ * @skb: buffer to segment
|
|
|
+ * @sg: whether scatter-gather can be used for generated segments
|
|
|
+ *
|
|
|
+ * 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 sg)
|
|
|
+{
|
|
|
+ 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 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, nsize;
|
|
|
+ int k;
|
|
|
+ int size;
|
|
|
+
|
|
|
+ len = skb->len - offset;
|
|
|
+ if (len > mss)
|
|
|
+ len = mss;
|
|
|
+
|
|
|
+ hsize = skb_headlen(skb) - offset;
|
|
|
+ if (hsize < 0)
|
|
|
+ hsize = 0;
|
|
|
+ nsize = hsize + doffset;
|
|
|
+ if (nsize > len + doffset || !sg)
|
|
|
+ nsize = len + doffset;
|
|
|
+
|
|
|
+ nskb = alloc_skb(nsize + 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_HW;
|
|
|
+ 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",
|