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: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Herbert Xu 2008-12-15 23:26:06 -08:00 коммит произвёл David S. Miller
Родитель eb14f01959
Коммит 89319d3801
1 изменённых файлов: 59 добавлений и 14 удалений

Просмотреть файл

@ -2428,6 +2428,7 @@ struct sk_buff *skb_segment(struct sk_buff *skb, int features)
{ {
struct sk_buff *segs = NULL; struct sk_buff *segs = NULL;
struct sk_buff *tail = 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 mss = skb_shinfo(skb)->gso_size;
unsigned int doffset = skb->data - skb_mac_header(skb); unsigned int doffset = skb->data - skb_mac_header(skb);
unsigned int offset = doffset; unsigned int offset = doffset;
@ -2447,7 +2448,6 @@ struct sk_buff *skb_segment(struct sk_buff *skb, int features)
struct sk_buff *nskb; struct sk_buff *nskb;
skb_frag_t *frag; skb_frag_t *frag;
int hsize; int hsize;
int k;
int size; int size;
len = skb->len - offset; len = skb->len - offset;
@ -2460,9 +2460,36 @@ struct sk_buff *skb_segment(struct sk_buff *skb, int features)
if (hsize > len || !sg) if (hsize > len || !sg)
hsize = len; hsize = len;
nskb = alloc_skb(hsize + doffset + headroom, GFP_ATOMIC); if (!hsize && i >= nfrags) {
if (unlikely(!nskb)) BUG_ON(fskb->len != len);
goto err;
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) if (segs)
tail->next = nskb; tail->next = nskb;
@ -2473,13 +2500,15 @@ struct sk_buff *skb_segment(struct sk_buff *skb, int features)
__copy_skb_header(nskb, skb); __copy_skb_header(nskb, skb);
nskb->mac_len = skb->mac_len; nskb->mac_len = skb->mac_len;
skb_reserve(nskb, headroom);
skb_reset_mac_header(nskb); skb_reset_mac_header(nskb);
skb_set_network_header(nskb, skb->mac_len); skb_set_network_header(nskb, skb->mac_len);
nskb->transport_header = (nskb->network_header + nskb->transport_header = (nskb->network_header +
skb_network_header_len(skb)); skb_network_header_len(skb));
skb_copy_from_linear_data(skb, skb_put(nskb, doffset), skb_copy_from_linear_data(skb, nskb->data, doffset);
doffset);
if (pos >= offset + len)
continue;
if (!sg) { if (!sg) {
nskb->ip_summed = CHECKSUM_NONE; nskb->ip_summed = CHECKSUM_NONE;
nskb->csum = skb_copy_and_csum_bits(skb, offset, 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; frag = skb_shinfo(nskb)->frags;
k = 0;
skb_copy_from_linear_data_offset(skb, offset, skb_copy_from_linear_data_offset(skb, offset,
skb_put(nskb, hsize), hsize); skb_put(nskb, hsize), hsize);
while (pos < offset + len) { while (pos < offset + len && i < nfrags) {
BUG_ON(i >= nfrags);
*frag = skb_shinfo(skb)->frags[i]; *frag = skb_shinfo(skb)->frags[i];
get_page(frag->page); get_page(frag->page);
size = frag->size; size = frag->size;
@ -2506,20 +2532,39 @@ struct sk_buff *skb_segment(struct sk_buff *skb, int features)
frag->size -= offset - pos; frag->size -= offset - pos;
} }
k++; skb_shinfo(nskb)->nr_frags++;
if (pos + size <= offset + len) { if (pos + size <= offset + len) {
i++; i++;
pos += size; pos += size;
} else { } else {
frag->size -= pos + size - (offset + len); frag->size -= pos + size - (offset + len);
break; goto skip_fraglist;
} }
frag++; 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->data_len = len - hsize;
nskb->len += nskb->data_len; nskb->len += nskb->data_len;
nskb->truesize += nskb->data_len; nskb->truesize += nskb->data_len;