net: Add skb_gro_receive
authorHerbert Xu <herbert@gondor.apana.org.au>
Tue, 16 Dec 2008 07:42:33 +0000 (23:42 -0800)
committerDavid S. Miller <davem@davemloft.net>
Tue, 16 Dec 2008 07:42:33 +0000 (23:42 -0800)
This patch adds the helper skb_gro_receive to merge packets for
GRO.  The current method is to allocate a new header skb and then
chain the original packets to its frag_list.  This is done to
make it easier to integrate into the existing GSO framework.

In future as GSO is moved into the drivers, we can undo this and
simply chain the original packets together.

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

index acf17af45af99055a289897bf6f9612e521ac6d4..cf2cb50f77d1fe5f19e9894b78f4532789054ce7 100644 (file)
@@ -1687,6 +1687,8 @@ extern int               skb_shift(struct sk_buff *tgt, struct sk_buff *skb,
                                 int shiftlen);
 
 extern struct sk_buff *skb_segment(struct sk_buff *skb, int features);
+extern int            skb_gro_receive(struct sk_buff **head,
+                                      struct sk_buff *skb);
 
 static inline void *skb_header_pointer(const struct sk_buff *skb, int offset,
                                       int len, void *buffer)
index 18e224af05a632a213fdd19634dc290f01491646..b8d0abb264334d6d5589d107c8cf4007e393ffd4 100644 (file)
@@ -2582,6 +2582,65 @@ err:
 
 EXPORT_SYMBOL_GPL(skb_segment);
 
+int skb_gro_receive(struct sk_buff **head, struct sk_buff *skb)
+{
+       struct sk_buff *p = *head;
+       struct sk_buff *nskb;
+       unsigned int headroom;
+       unsigned int hlen = p->data - skb_mac_header(p);
+
+       if (hlen + p->len + skb->len >= 65536)
+               return -E2BIG;
+
+       if (skb_shinfo(p)->frag_list)
+               goto merge;
+
+       headroom = skb_headroom(p);
+       nskb = netdev_alloc_skb(p->dev, headroom);
+       if (unlikely(!nskb))
+               return -ENOMEM;
+
+       __copy_skb_header(nskb, p);
+       nskb->mac_len = p->mac_len;
+
+       skb_reserve(nskb, headroom);
+
+       skb_set_mac_header(nskb, -hlen);
+       skb_set_network_header(nskb, skb_network_offset(p));
+       skb_set_transport_header(nskb, skb_transport_offset(p));
+
+       memcpy(skb_mac_header(nskb), skb_mac_header(p), hlen);
+
+       *NAPI_GRO_CB(nskb) = *NAPI_GRO_CB(p);
+       skb_shinfo(nskb)->frag_list = p;
+       skb_header_release(p);
+       nskb->prev = p;
+
+       nskb->data_len += p->len;
+       nskb->truesize += p->len;
+       nskb->len += p->len;
+
+       *head = nskb;
+       nskb->next = p->next;
+       p->next = NULL;
+
+       p = nskb;
+
+merge:
+       NAPI_GRO_CB(p)->count++;
+       p->prev->next = skb;
+       p->prev = skb;
+       skb_header_release(skb);
+
+       p->data_len += skb->len;
+       p->truesize += skb->len;
+       p->len += skb->len;
+
+       NAPI_GRO_CB(skb)->same_flow = 1;
+       return 0;
+}
+EXPORT_SYMBOL_GPL(skb_gro_receive);
+
 void __init skb_init(void)
 {
        skbuff_head_cache = kmem_cache_create("skbuff_head_cache",