[funini.com] -> [kei@sodan] -> Kernel Reading

root/net/xfrm/xfrm_ipcomp.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. ipcomp_decompress
  2. ipcomp_input
  3. ipcomp_compress
  4. ipcomp_output
  5. ipcomp_free_scratches
  6. ipcomp_alloc_scratches
  7. ipcomp_free_tfms
  8. ipcomp_alloc_tfms
  9. ipcomp_free_data
  10. ipcomp_destroy
  11. ipcomp_init_state

/*
 * IP Payload Compression Protocol (IPComp) - RFC3173.
 *
 * Copyright (c) 2003 James Morris <jmorris@intercode.com.au>
 * Copyright (c) 2003-2008 Herbert Xu <herbert@gondor.apana.org.au>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * Todo:
 *   - Tunable compression parameters.
 *   - Compression stats.
 *   - Adaptive compression.
 */

#include <linux/crypto.h>
#include <linux/err.h>
#include <linux/gfp.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/percpu.h>
#include <linux/smp.h>
#include <linux/vmalloc.h>
#include <net/ip.h>
#include <net/ipcomp.h>
#include <net/xfrm.h>

struct ipcomp_tfms {
        struct list_head list;
        struct crypto_comp **tfms;
        int users;
};

static DEFINE_MUTEX(ipcomp_resource_mutex);
static void **ipcomp_scratches;
static int ipcomp_scratch_users;
static LIST_HEAD(ipcomp_tfms_list);

static int ipcomp_decompress(struct xfrm_state *x, struct sk_buff *skb)
{
        struct ipcomp_data *ipcd = x->data;
        const int plen = skb->len;
        int dlen = IPCOMP_SCRATCH_SIZE;
        const u8 *start = skb->data;
        const int cpu = get_cpu();
        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;

        if (dlen < (plen + sizeof(struct ip_comp_hdr))) {
                err = -EINVAL;
                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;

out:
        put_cpu();
        return err;
}

int ipcomp_input(struct xfrm_state *x, struct sk_buff *skb)
{
        int nexthdr;
        int err = -ENOMEM;
        struct ip_comp_hdr *ipch;

        if (skb_linearize_cow(skb))
                goto out;

        skb->ip_summed = CHECKSUM_NONE;

        /* Remove ipcomp header and decompress original payload */
        ipch = (void *)skb->data;
        nexthdr = ipch->nexthdr;

        skb->transport_header = skb->network_header + sizeof(*ipch);
        __skb_pull(skb, sizeof(*ipch));
        err = ipcomp_decompress(x, skb);
        if (err)
                goto out;

        err = nexthdr;

out:
        return err;
}
EXPORT_SYMBOL_GPL(ipcomp_input);

static int ipcomp_compress(struct xfrm_state *x, struct sk_buff *skb)
{
        struct ipcomp_data *ipcd = x->data;
        const int plen = skb->len;
        int dlen = IPCOMP_SCRATCH_SIZE;
        u8 *start = skb->data;
        const int cpu = get_cpu();
        u8 *scratch = *per_cpu_ptr(ipcomp_scratches, cpu);
        struct crypto_comp *tfm = *per_cpu_ptr(ipcd->tfms, cpu);
        int err;

        local_bh_disable();
        err = crypto_comp_compress(tfm, start, plen, scratch, &dlen);
        local_bh_enable();
        if (err)
                goto out;

        if ((dlen + sizeof(struct ip_comp_hdr)) >= plen) {
                err = -EMSGSIZE;
                goto out;
        }

        memcpy(start + sizeof(struct ip_comp_hdr), scratch, dlen);
        put_cpu();

        pskb_trim(skb, dlen + sizeof(struct ip_comp_hdr));
        return 0;

out:
        put_cpu();
        return err;
}

int ipcomp_output(struct xfrm_state *x, struct sk_buff *skb)
{
        int err;
        struct ip_comp_hdr *ipch;
        struct ipcomp_data *ipcd = x->data;

        if (skb->len < ipcd->threshold) {
                /* Don't bother compressing */
                goto out_ok;
        }

        if (skb_linearize_cow(skb))
                goto out_ok;

        err = ipcomp_compress(x, skb);

        if (err) {
                goto out_ok;
        }

        /* Install ipcomp header, convert into ipcomp datagram. */
        ipch = ip_comp_hdr(skb);
        ipch->nexthdr = *skb_mac_header(skb);
        ipch->flags = 0;
        ipch->cpi = htons((u16 )ntohl(x->id.spi));
        *skb_mac_header(skb) = IPPROTO_COMP;
out_ok:
        skb_push(skb, -skb_network_offset(skb));
        return 0;
}
EXPORT_SYMBOL_GPL(ipcomp_output);

static void ipcomp_free_scratches(void)
{
        int i;
        void **scratches;

        if (--ipcomp_scratch_users)
                return;

        scratches = ipcomp_scratches;
        if (!scratches)
                return;

        for_each_possible_cpu(i)
                vfree(*per_cpu_ptr(scratches, i));

        free_percpu(scratches);
}

static void **ipcomp_alloc_scratches(void)
{
        int i;
        void **scratches;

        if (ipcomp_scratch_users++)
                return ipcomp_scratches;

        scratches = alloc_percpu(void *);
        if (!scratches)
                return NULL;

        ipcomp_scratches = scratches;

        for_each_possible_cpu(i) {
                void *scratch = vmalloc(IPCOMP_SCRATCH_SIZE);
                if (!scratch)
                        return NULL;
                *per_cpu_ptr(scratches, i) = scratch;
        }

        return scratches;
}

static void ipcomp_free_tfms(struct crypto_comp **tfms)
{
        struct ipcomp_tfms *pos;
        int cpu;

        list_for_each_entry(pos, &ipcomp_tfms_list, list) {
                if (pos->tfms == tfms)
                        break;
        }

        WARN_ON(!pos);

        if (--pos->users)
                return;

        list_del(&pos->list);
        kfree(pos);

        if (!tfms)
                return;

        for_each_possible_cpu(cpu) {
                struct crypto_comp *tfm = *per_cpu_ptr(tfms, cpu);
                crypto_free_comp(tfm);
        }
        free_percpu(tfms);
}

static struct crypto_comp **ipcomp_alloc_tfms(const char *alg_name)
{
        struct ipcomp_tfms *pos;
        struct crypto_comp **tfms;
        int cpu;

        /* This can be any valid CPU ID so we don't need locking. */
        cpu = raw_smp_processor_id();

        list_for_each_entry(pos, &ipcomp_tfms_list, list) {
                struct crypto_comp *tfm;

                tfms = pos->tfms;
                tfm = *per_cpu_ptr(tfms, cpu);

                if (!strcmp(crypto_comp_name(tfm), alg_name)) {
                        pos->users++;
                        return tfms;
                }
        }

        pos = kmalloc(sizeof(*pos), GFP_KERNEL);
        if (!pos)
                return NULL;

        pos->users = 1;
        INIT_LIST_HEAD(&pos->list);
        list_add(&pos->list, &ipcomp_tfms_list);

        pos->tfms = tfms = alloc_percpu(struct crypto_comp *);
        if (!tfms)
                goto error;

        for_each_possible_cpu(cpu) {
                struct crypto_comp *tfm = crypto_alloc_comp(alg_name, 0,
                                                            CRYPTO_ALG_ASYNC);
                if (IS_ERR(tfm))
                        goto error;
                *per_cpu_ptr(tfms, cpu) = tfm;
        }

        return tfms;

error:
        ipcomp_free_tfms(tfms);
        return NULL;
}

static void ipcomp_free_data(struct ipcomp_data *ipcd)
{
        if (ipcd->tfms)
                ipcomp_free_tfms(ipcd->tfms);
        ipcomp_free_scratches();
}

void ipcomp_destroy(struct xfrm_state *x)
{
        struct ipcomp_data *ipcd = x->data;
        if (!ipcd)
                return;
        xfrm_state_delete_tunnel(x);
        mutex_lock(&ipcomp_resource_mutex);
        ipcomp_free_data(ipcd);
        mutex_unlock(&ipcomp_resource_mutex);
        kfree(ipcd);
}
EXPORT_SYMBOL_GPL(ipcomp_destroy);

int ipcomp_init_state(struct xfrm_state *x)
{
        int err;
        struct ipcomp_data *ipcd;
        struct xfrm_algo_desc *calg_desc;

        err = -EINVAL;
        if (!x->calg)
                goto out;

        if (x->encap)
                goto out;

        err = -ENOMEM;
        ipcd = kzalloc(sizeof(*ipcd), GFP_KERNEL);
        if (!ipcd)
                goto out;

        mutex_lock(&ipcomp_resource_mutex);
        if (!ipcomp_alloc_scratches())
                goto error;

        ipcd->tfms = ipcomp_alloc_tfms(x->calg->alg_name);
        if (!ipcd->tfms)
                goto error;
        mutex_unlock(&ipcomp_resource_mutex);

        calg_desc = xfrm_calg_get_byname(x->calg->alg_name, 0);
        BUG_ON(!calg_desc);
        ipcd->threshold = calg_desc->uinfo.comp.threshold;
        x->data = ipcd;
        err = 0;
out:
        return err;

error:
        ipcomp_free_data(ipcd);
        mutex_unlock(&ipcomp_resource_mutex);
        kfree(ipcd);
        goto out;
}
EXPORT_SYMBOL_GPL(ipcomp_init_state);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("IP Payload Compression Protocol (IPComp) - RFC3173");
MODULE_AUTHOR("James Morris <jmorris@intercode.com.au>");

/* [<][>][^][v][top][bottom][index][help] */

[funini.com] -> [kei@sodan] -> Kernel Reading