diff options
| author | Herbert Xu <herbert@gondor.apana.org.au> | 2008-07-25 02:55:33 -0700 | 
|---|---|---|
| committer | David S. Miller <davem@davemloft.net> | 2008-07-25 02:55:33 -0700 | 
| commit | 7d7e5a60c62e88cb8782760bb6c4d3bd1577a6c6 (patch) | |
| tree | c6bedd62097698466d7dc0b5166e4ed29e1b6ee5 /net/xfrm/xfrm_ipcomp.c | |
| parent | 6fccab671f2f0a24b799f29a4ec878f62d34656c (diff) | |
| download | olio-linux-3.10-7d7e5a60c62e88cb8782760bb6c4d3bd1577a6c6.tar.xz olio-linux-3.10-7d7e5a60c62e88cb8782760bb6c4d3bd1577a6c6.zip  | |
ipsec: ipcomp - Decompress into frags if necessary
When decompressing extremely large packets allocating them through
kmalloc is prone to failure.  Therefore it's better to use page
frags instead.
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/xfrm/xfrm_ipcomp.c')
| -rw-r--r-- | net/xfrm/xfrm_ipcomp.c | 48 | 
1 files changed, 42 insertions, 6 deletions
diff --git a/net/xfrm/xfrm_ipcomp.c b/net/xfrm/xfrm_ipcomp.c index b51e804fbba..800f669083f 100644 --- a/net/xfrm/xfrm_ipcomp.c +++ b/net/xfrm/xfrm_ipcomp.c @@ -17,6 +17,7 @@  #include <linux/crypto.h>  #include <linux/err.h> +#include <linux/gfp.h>  #include <linux/list.h>  #include <linux/module.h>  #include <linux/mutex.h> @@ -49,6 +50,7 @@ static int ipcomp_decompress(struct xfrm_state *x, struct sk_buff *skb)  	u8 *scratch = *per_cpu_ptr(ipcomp_scratches, cpu);  	struct crypto_comp *tfm = *per_cpu_ptr(ipcd->tfms, cpu);  	int err = crypto_comp_decompress(tfm, start, plen, scratch, &dlen); +	int len;  	if (err)  		goto out; @@ -58,13 +60,47 @@ static int ipcomp_decompress(struct xfrm_state *x, struct sk_buff *skb)  		goto out;  	} -	err = pskb_expand_head(skb, 0, dlen - plen, GFP_ATOMIC); -	if (err) -		goto out; +	len = dlen - plen; +	if (len > skb_tailroom(skb)) +		len = skb_tailroom(skb); + +	skb->truesize += len; +	__skb_put(skb, len); + +	len += plen; +	skb_copy_to_linear_data(skb, scratch, len); + +	while ((scratch += len, dlen -= len) > 0) { +		skb_frag_t *frag; + +		err = -EMSGSIZE; +		if (WARN_ON(skb_shinfo(skb)->nr_frags >= MAX_SKB_FRAGS)) +			goto out; + +		frag = skb_shinfo(skb)->frags + skb_shinfo(skb)->nr_frags; +		frag->page = alloc_page(GFP_ATOMIC); + +		err = -ENOMEM; +		if (!frag->page) +			goto out; + +		len = PAGE_SIZE; +		if (dlen < len) +			len = dlen; + +		memcpy(page_address(frag->page), scratch, len); + +		frag->page_offset = 0; +		frag->size = len; +		skb->truesize += len; +		skb->data_len += len; +		skb->len += len; + +		skb_shinfo(skb)->nr_frags++; +	} + +	err = 0; -	skb->truesize += dlen - plen; -	__skb_put(skb, dlen - plen); -	skb_copy_to_linear_data(skb, scratch, dlen);  out:  	put_cpu();  	return err;  |