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 | blackbird-op-linux-7d7e5a60c62e88cb8782760bb6c4d3bd1577a6c6.tar.gz blackbird-op-linux-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 b51e804fbbad..800f669083fb 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; |