Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 89319d38 authored by Herbert Xu's avatar Herbert Xu Committed by David S. Miller
Browse files

net: Add frag_list support to skb_segment



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: default avatarHerbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent eb14f019
Loading
Loading
Loading
Loading
+59 −14
Original line number Diff line number Diff line
@@ -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,10 +2460,37 @@ 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 (!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;
		else
@@ -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;