linux/net/xfrm/xfrm_ipcomp.c
<<
>>
Prefs
   1/*
   2 * IP Payload Compression Protocol (IPComp) - RFC3173.
   3 *
   4 * Copyright (c) 2003 James Morris <jmorris@intercode.com.au>
   5 * Copyright (c) 2003-2008 Herbert Xu <herbert@gondor.apana.org.au>
   6 *
   7 * This program is free software; you can redistribute it and/or modify it
   8 * under the terms of the GNU General Public License as published by the Free
   9 * Software Foundation; either version 2 of the License, or (at your option)
  10 * any later version.
  11 *
  12 * Todo:
  13 *   - Tunable compression parameters.
  14 *   - Compression stats.
  15 *   - Adaptive compression.
  16 */
  17
  18#include <linux/crypto.h>
  19#include <linux/err.h>
  20#include <linux/gfp.h>
  21#include <linux/list.h>
  22#include <linux/module.h>
  23#include <linux/mutex.h>
  24#include <linux/percpu.h>
  25#include <linux/smp.h>
  26#include <linux/vmalloc.h>
  27#include <net/ip.h>
  28#include <net/ipcomp.h>
  29#include <net/xfrm.h>
  30
  31struct ipcomp_tfms {
  32        struct list_head list;
  33        struct crypto_comp **tfms;
  34        int users;
  35};
  36
  37static DEFINE_MUTEX(ipcomp_resource_mutex);
  38static void **ipcomp_scratches;
  39static int ipcomp_scratch_users;
  40static LIST_HEAD(ipcomp_tfms_list);
  41
  42static int ipcomp_decompress(struct xfrm_state *x, struct sk_buff *skb)
  43{
  44        struct ipcomp_data *ipcd = x->data;
  45        const int plen = skb->len;
  46        int dlen = IPCOMP_SCRATCH_SIZE;
  47        const u8 *start = skb->data;
  48        const int cpu = get_cpu();
  49        u8 *scratch = *per_cpu_ptr(ipcomp_scratches, cpu);
  50        struct crypto_comp *tfm = *per_cpu_ptr(ipcd->tfms, cpu);
  51        int err = crypto_comp_decompress(tfm, start, plen, scratch, &dlen);
  52        int len;
  53
  54        if (err)
  55                goto out;
  56
  57        if (dlen < (plen + sizeof(struct ip_comp_hdr))) {
  58                err = -EINVAL;
  59                goto out;
  60        }
  61
  62        len = dlen - plen;
  63        if (len > skb_tailroom(skb))
  64                len = skb_tailroom(skb);
  65
  66        __skb_put(skb, len);
  67
  68        len += plen;
  69        skb_copy_to_linear_data(skb, scratch, len);
  70
  71        while ((scratch += len, dlen -= len) > 0) {
  72                skb_frag_t *frag;
  73
  74                err = -EMSGSIZE;
  75                if (WARN_ON(skb_shinfo(skb)->nr_frags >= MAX_SKB_FRAGS))
  76                        goto out;
  77
  78                frag = skb_shinfo(skb)->frags + skb_shinfo(skb)->nr_frags;
  79                frag->page = alloc_page(GFP_ATOMIC);
  80
  81                err = -ENOMEM;
  82                if (!frag->page)
  83                        goto out;
  84
  85                len = PAGE_SIZE;
  86                if (dlen < len)
  87                        len = dlen;
  88
  89                memcpy(page_address(frag->page), scratch, len);
  90
  91                frag->page_offset = 0;
  92                frag->size = len;
  93                skb->truesize += len;
  94                skb->data_len += len;
  95                skb->len += len;
  96
  97                skb_shinfo(skb)->nr_frags++;
  98        }
  99
 100        err = 0;
 101
 102out:
 103        put_cpu();
 104        return err;
 105}
 106
 107int ipcomp_input(struct xfrm_state *x, struct sk_buff *skb)
 108{
 109        int nexthdr;
 110        int err = -ENOMEM;
 111        struct ip_comp_hdr *ipch;
 112
 113        if (skb_linearize_cow(skb))
 114                goto out;
 115
 116        skb->ip_summed = CHECKSUM_NONE;
 117
 118        /* Remove ipcomp header and decompress original payload */
 119        ipch = (void *)skb->data;
 120        nexthdr = ipch->nexthdr;
 121
 122        skb->transport_header = skb->network_header + sizeof(*ipch);
 123        __skb_pull(skb, sizeof(*ipch));
 124        err = ipcomp_decompress(x, skb);
 125        if (err)
 126                goto out;
 127
 128        err = nexthdr;
 129
 130out:
 131        return err;
 132}
 133EXPORT_SYMBOL_GPL(ipcomp_input);
 134
 135static int ipcomp_compress(struct xfrm_state *x, struct sk_buff *skb)
 136{
 137        struct ipcomp_data *ipcd = x->data;
 138        const int plen = skb->len;
 139        int dlen = IPCOMP_SCRATCH_SIZE;
 140        u8 *start = skb->data;
 141        const int cpu = get_cpu();
 142        u8 *scratch = *per_cpu_ptr(ipcomp_scratches, cpu);
 143        struct crypto_comp *tfm = *per_cpu_ptr(ipcd->tfms, cpu);
 144        int err;
 145
 146        local_bh_disable();
 147        err = crypto_comp_compress(tfm, start, plen, scratch, &dlen);
 148        local_bh_enable();
 149        if (err)
 150                goto out;
 151
 152        if ((dlen + sizeof(struct ip_comp_hdr)) >= plen) {
 153                err = -EMSGSIZE;
 154                goto out;
 155        }
 156
 157        memcpy(start + sizeof(struct ip_comp_hdr), scratch, dlen);
 158        put_cpu();
 159
 160        pskb_trim(skb, dlen + sizeof(struct ip_comp_hdr));
 161        return 0;
 162
 163out:
 164        put_cpu();
 165        return err;
 166}
 167
 168int ipcomp_output(struct xfrm_state *x, struct sk_buff *skb)
 169{
 170        int err;
 171        struct ip_comp_hdr *ipch;
 172        struct ipcomp_data *ipcd = x->data;
 173
 174        if (skb->len < ipcd->threshold) {
 175                /* Don't bother compressing */
 176                goto out_ok;
 177        }
 178
 179        if (skb_linearize_cow(skb))
 180                goto out_ok;
 181
 182        err = ipcomp_compress(x, skb);
 183
 184        if (err) {
 185                goto out_ok;
 186        }
 187
 188        /* Install ipcomp header, convert into ipcomp datagram. */
 189        ipch = ip_comp_hdr(skb);
 190        ipch->nexthdr = *skb_mac_header(skb);
 191        ipch->flags = 0;
 192        ipch->cpi = htons((u16 )ntohl(x->id.spi));
 193        *skb_mac_header(skb) = IPPROTO_COMP;
 194out_ok:
 195        skb_push(skb, -skb_network_offset(skb));
 196        return 0;
 197}
 198EXPORT_SYMBOL_GPL(ipcomp_output);
 199
 200static void ipcomp_free_scratches(void)
 201{
 202        int i;
 203        void **scratches;
 204
 205        if (--ipcomp_scratch_users)
 206                return;
 207
 208        scratches = ipcomp_scratches;
 209        if (!scratches)
 210                return;
 211
 212        for_each_possible_cpu(i)
 213                vfree(*per_cpu_ptr(scratches, i));
 214
 215        free_percpu(scratches);
 216}
 217
 218static void **ipcomp_alloc_scratches(void)
 219{
 220        int i;
 221        void **scratches;
 222
 223        if (ipcomp_scratch_users++)
 224                return ipcomp_scratches;
 225
 226        scratches = alloc_percpu(void *);
 227        if (!scratches)
 228                return NULL;
 229
 230        ipcomp_scratches = scratches;
 231
 232        for_each_possible_cpu(i) {
 233                void *scratch = vmalloc(IPCOMP_SCRATCH_SIZE);
 234                if (!scratch)
 235                        return NULL;
 236                *per_cpu_ptr(scratches, i) = scratch;
 237        }
 238
 239        return scratches;
 240}
 241
 242static void ipcomp_free_tfms(struct crypto_comp **tfms)
 243{
 244        struct ipcomp_tfms *pos;
 245        int cpu;
 246
 247        list_for_each_entry(pos, &ipcomp_tfms_list, list) {
 248                if (pos->tfms == tfms)
 249                        break;
 250        }
 251
 252        WARN_ON(!pos);
 253
 254        if (--pos->users)
 255                return;
 256
 257        list_del(&pos->list);
 258        kfree(pos);
 259
 260        if (!tfms)
 261                return;
 262
 263        for_each_possible_cpu(cpu) {
 264                struct crypto_comp *tfm = *per_cpu_ptr(tfms, cpu);
 265                crypto_free_comp(tfm);
 266        }
 267        free_percpu(tfms);
 268}
 269
 270static struct crypto_comp **ipcomp_alloc_tfms(const char *alg_name)
 271{
 272        struct ipcomp_tfms *pos;
 273        struct crypto_comp **tfms;
 274        int cpu;
 275
 276        /* This can be any valid CPU ID so we don't need locking. */
 277        cpu = raw_smp_processor_id();
 278
 279        list_for_each_entry(pos, &ipcomp_tfms_list, list) {
 280                struct crypto_comp *tfm;
 281
 282                tfms = pos->tfms;
 283                tfm = *per_cpu_ptr(tfms, cpu);
 284
 285                if (!strcmp(crypto_comp_name(tfm), alg_name)) {
 286                        pos->users++;
 287                        return tfms;
 288                }
 289        }
 290
 291        pos = kmalloc(sizeof(*pos), GFP_KERNEL);
 292        if (!pos)
 293                return NULL;
 294
 295        pos->users = 1;
 296        INIT_LIST_HEAD(&pos->list);
 297        list_add(&pos->list, &ipcomp_tfms_list);
 298
 299        pos->tfms = tfms = alloc_percpu(struct crypto_comp *);
 300        if (!tfms)
 301                goto error;
 302
 303        for_each_possible_cpu(cpu) {
 304                struct crypto_comp *tfm = crypto_alloc_comp(alg_name, 0,
 305                                                            CRYPTO_ALG_ASYNC);
 306                if (IS_ERR(tfm))
 307                        goto error;
 308                *per_cpu_ptr(tfms, cpu) = tfm;
 309        }
 310
 311        return tfms;
 312
 313error:
 314        ipcomp_free_tfms(tfms);
 315        return NULL;
 316}
 317
 318static void ipcomp_free_data(struct ipcomp_data *ipcd)
 319{
 320        if (ipcd->tfms)
 321                ipcomp_free_tfms(ipcd->tfms);
 322        ipcomp_free_scratches();
 323}
 324
 325void ipcomp_destroy(struct xfrm_state *x)
 326{
 327        struct ipcomp_data *ipcd = x->data;
 328        if (!ipcd)
 329                return;
 330        xfrm_state_delete_tunnel(x);
 331        mutex_lock(&ipcomp_resource_mutex);
 332        ipcomp_free_data(ipcd);
 333        mutex_unlock(&ipcomp_resource_mutex);
 334        kfree(ipcd);
 335}
 336EXPORT_SYMBOL_GPL(ipcomp_destroy);
 337
 338int ipcomp_init_state(struct xfrm_state *x)
 339{
 340        int err;
 341        struct ipcomp_data *ipcd;
 342        struct xfrm_algo_desc *calg_desc;
 343
 344        err = -EINVAL;
 345        if (!x->calg)
 346                goto out;
 347
 348        if (x->encap)
 349                goto out;
 350
 351        err = -ENOMEM;
 352        ipcd = kzalloc(sizeof(*ipcd), GFP_KERNEL);
 353        if (!ipcd)
 354                goto out;
 355
 356        mutex_lock(&ipcomp_resource_mutex);
 357        if (!ipcomp_alloc_scratches())
 358                goto error;
 359
 360        ipcd->tfms = ipcomp_alloc_tfms(x->calg->alg_name);
 361        if (!ipcd->tfms)
 362                goto error;
 363        mutex_unlock(&ipcomp_resource_mutex);
 364
 365        calg_desc = xfrm_calg_get_byname(x->calg->alg_name, 0);
 366        BUG_ON(!calg_desc);
 367        ipcd->threshold = calg_desc->uinfo.comp.threshold;
 368        x->data = ipcd;
 369        err = 0;
 370out:
 371        return err;
 372
 373error:
 374        ipcomp_free_data(ipcd);
 375        mutex_unlock(&ipcomp_resource_mutex);
 376        kfree(ipcd);
 377        goto out;
 378}
 379EXPORT_SYMBOL_GPL(ipcomp_init_state);
 380
 381MODULE_LICENSE("GPL");
 382MODULE_DESCRIPTION("IP Payload Compression Protocol (IPComp) - RFC3173");
 383MODULE_AUTHOR("James Morris <jmorris@intercode.com.au>");
 384