linux/net/ipv6/ipcomp6.c
<<
>>
Prefs
   1/*
   2 * IP Payload Compression Protocol (IPComp) for IPv6 - RFC3173
   3 *
   4 * Copyright (C)2003 USAGI/WIDE Project
   5 *
   6 * Author       Mitsuru KANDA  <mk@linux-ipv6.org>
   7 *
   8 * This program is free software; you can redistribute it and/or modify
   9 * it under the terms of the GNU General Public License as published by
  10 * the Free Software Foundation; either version 2 of the License, or
  11 * (at your option) any later version.
  12 *
  13 * This program is distributed in the hope that it will be useful,
  14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16 * GNU General Public License for more details.
  17 *
  18 * You should have received a copy of the GNU General Public License
  19 * along with this program; if not, write to the Free Software
  20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  21 */
  22/*
  23 * [Memo]
  24 *
  25 * Outbound:
  26 *  The compression of IP datagram MUST be done before AH/ESP processing,
  27 *  fragmentation, and the addition of Hop-by-Hop/Routing header.
  28 *
  29 * Inbound:
  30 *  The decompression of IP datagram MUST be done after the reassembly,
  31 *  AH/ESP processing.
  32 */
  33#include <linux/module.h>
  34#include <net/ip.h>
  35#include <net/xfrm.h>
  36#include <net/ipcomp.h>
  37#include <asm/semaphore.h>
  38#include <linux/crypto.h>
  39#include <linux/err.h>
  40#include <linux/pfkeyv2.h>
  41#include <linux/random.h>
  42#include <linux/percpu.h>
  43#include <linux/smp.h>
  44#include <linux/list.h>
  45#include <linux/vmalloc.h>
  46#include <linux/rtnetlink.h>
  47#include <net/icmp.h>
  48#include <net/ipv6.h>
  49#include <net/protocol.h>
  50#include <linux/ipv6.h>
  51#include <linux/icmpv6.h>
  52#include <linux/mutex.h>
  53
  54struct ipcomp6_tfms {
  55        struct list_head list;
  56        struct crypto_comp **tfms;
  57        int users;
  58};
  59
  60static DEFINE_MUTEX(ipcomp6_resource_mutex);
  61static void **ipcomp6_scratches;
  62static int ipcomp6_scratch_users;
  63static LIST_HEAD(ipcomp6_tfms_list);
  64
  65static int ipcomp6_input(struct xfrm_state *x, struct sk_buff *skb)
  66{
  67        int err = -ENOMEM;
  68        struct ip_comp_hdr *ipch;
  69        int plen, dlen;
  70        struct ipcomp_data *ipcd = x->data;
  71        u8 *start, *scratch;
  72        struct crypto_comp *tfm;
  73        int cpu;
  74
  75        if (skb_linearize_cow(skb))
  76                goto out;
  77
  78        skb->ip_summed = CHECKSUM_NONE;
  79
  80        /* Remove ipcomp header and decompress original payload */
  81        ipch = (void *)skb->data;
  82        skb->transport_header = skb->network_header + sizeof(*ipch);
  83        __skb_pull(skb, sizeof(*ipch));
  84
  85        /* decompression */
  86        plen = skb->len;
  87        dlen = IPCOMP_SCRATCH_SIZE;
  88        start = skb->data;
  89
  90        cpu = get_cpu();
  91        scratch = *per_cpu_ptr(ipcomp6_scratches, cpu);
  92        tfm = *per_cpu_ptr(ipcd->tfms, cpu);
  93
  94        err = crypto_comp_decompress(tfm, start, plen, scratch, &dlen);
  95        if (err)
  96                goto out_put_cpu;
  97
  98        if (dlen < (plen + sizeof(*ipch))) {
  99                err = -EINVAL;
 100                goto out_put_cpu;
 101        }
 102
 103        err = pskb_expand_head(skb, 0, dlen - plen, GFP_ATOMIC);
 104        if (err) {
 105                goto out_put_cpu;
 106        }
 107
 108        skb->truesize += dlen - plen;
 109        __skb_put(skb, dlen - plen);
 110        skb_copy_to_linear_data(skb, scratch, dlen);
 111        err = ipch->nexthdr;
 112
 113out_put_cpu:
 114        put_cpu();
 115out:
 116        return err;
 117}
 118
 119static int ipcomp6_output(struct xfrm_state *x, struct sk_buff *skb)
 120{
 121        int err;
 122        struct ip_comp_hdr *ipch;
 123        struct ipcomp_data *ipcd = x->data;
 124        int plen, dlen;
 125        u8 *start, *scratch;
 126        struct crypto_comp *tfm;
 127        int cpu;
 128
 129        /* check whether datagram len is larger than threshold */
 130        if (skb->len < ipcd->threshold) {
 131                goto out_ok;
 132        }
 133
 134        if (skb_linearize_cow(skb))
 135                goto out_ok;
 136
 137        /* compression */
 138        plen = skb->len;
 139        dlen = IPCOMP_SCRATCH_SIZE;
 140        start = skb->data;
 141
 142        cpu = get_cpu();
 143        scratch = *per_cpu_ptr(ipcomp6_scratches, cpu);
 144        tfm = *per_cpu_ptr(ipcd->tfms, cpu);
 145
 146        err = crypto_comp_compress(tfm, start, plen, scratch, &dlen);
 147        if (err || (dlen + sizeof(*ipch)) >= plen) {
 148                put_cpu();
 149                goto out_ok;
 150        }
 151        memcpy(start + sizeof(struct ip_comp_hdr), scratch, dlen);
 152        put_cpu();
 153        pskb_trim(skb, dlen + sizeof(struct ip_comp_hdr));
 154
 155        /* insert ipcomp header and replace datagram */
 156        ipch = ip_comp_hdr(skb);
 157        ipch->nexthdr = *skb_mac_header(skb);
 158        ipch->flags = 0;
 159        ipch->cpi = htons((u16 )ntohl(x->id.spi));
 160        *skb_mac_header(skb) = IPPROTO_COMP;
 161
 162out_ok:
 163        skb_push(skb, -skb_network_offset(skb));
 164
 165        return 0;
 166}
 167
 168static void ipcomp6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
 169                                int type, int code, int offset, __be32 info)
 170{
 171        __be32 spi;
 172        struct ipv6hdr *iph = (struct ipv6hdr*)skb->data;
 173        struct ip_comp_hdr *ipcomph =
 174                (struct ip_comp_hdr *)(skb->data + offset);
 175        struct xfrm_state *x;
 176
 177        if (type != ICMPV6_DEST_UNREACH && type != ICMPV6_PKT_TOOBIG)
 178                return;
 179
 180        spi = htonl(ntohs(ipcomph->cpi));
 181        x = xfrm_state_lookup((xfrm_address_t *)&iph->daddr, spi, IPPROTO_COMP, AF_INET6);
 182        if (!x)
 183                return;
 184
 185        printk(KERN_DEBUG "pmtu discovery on SA IPCOMP/%08x/" NIP6_FMT "\n",
 186                        spi, NIP6(iph->daddr));
 187        xfrm_state_put(x);
 188}
 189
 190static struct xfrm_state *ipcomp6_tunnel_create(struct xfrm_state *x)
 191{
 192        struct xfrm_state *t = NULL;
 193        u8 mode = XFRM_MODE_TUNNEL;
 194
 195        t = xfrm_state_alloc();
 196        if (!t)
 197                goto out;
 198
 199        t->id.proto = IPPROTO_IPV6;
 200        t->id.spi = xfrm6_tunnel_alloc_spi((xfrm_address_t *)&x->props.saddr);
 201        if (!t->id.spi)
 202                goto error;
 203
 204        memcpy(t->id.daddr.a6, x->id.daddr.a6, sizeof(struct in6_addr));
 205        memcpy(&t->sel, &x->sel, sizeof(t->sel));
 206        t->props.family = AF_INET6;
 207        if (x->props.mode == XFRM_MODE_BEET)
 208                mode = x->props.mode;
 209        t->props.mode = mode;
 210        memcpy(t->props.saddr.a6, x->props.saddr.a6, sizeof(struct in6_addr));
 211
 212        if (xfrm_init_state(t))
 213                goto error;
 214
 215        atomic_set(&t->tunnel_users, 1);
 216
 217out:
 218        return t;
 219
 220error:
 221        t->km.state = XFRM_STATE_DEAD;
 222        xfrm_state_put(t);
 223        t = NULL;
 224        goto out;
 225}
 226
 227static int ipcomp6_tunnel_attach(struct xfrm_state *x)
 228{
 229        int err = 0;
 230        struct xfrm_state *t = NULL;
 231        __be32 spi;
 232
 233        spi = xfrm6_tunnel_spi_lookup((xfrm_address_t *)&x->props.saddr);
 234        if (spi)
 235                t = xfrm_state_lookup((xfrm_address_t *)&x->id.daddr,
 236                                              spi, IPPROTO_IPV6, AF_INET6);
 237        if (!t) {
 238                t = ipcomp6_tunnel_create(x);
 239                if (!t) {
 240                        err = -EINVAL;
 241                        goto out;
 242                }
 243                xfrm_state_insert(t);
 244                xfrm_state_hold(t);
 245        }
 246        x->tunnel = t;
 247        atomic_inc(&t->tunnel_users);
 248
 249out:
 250        return err;
 251}
 252
 253static void ipcomp6_free_scratches(void)
 254{
 255        int i;
 256        void **scratches;
 257
 258        if (--ipcomp6_scratch_users)
 259                return;
 260
 261        scratches = ipcomp6_scratches;
 262        if (!scratches)
 263                return;
 264
 265        for_each_possible_cpu(i) {
 266                void *scratch = *per_cpu_ptr(scratches, i);
 267
 268                vfree(scratch);
 269        }
 270
 271        free_percpu(scratches);
 272}
 273
 274static void **ipcomp6_alloc_scratches(void)
 275{
 276        int i;
 277        void **scratches;
 278
 279        if (ipcomp6_scratch_users++)
 280                return ipcomp6_scratches;
 281
 282        scratches = alloc_percpu(void *);
 283        if (!scratches)
 284                return NULL;
 285
 286        ipcomp6_scratches = scratches;
 287
 288        for_each_possible_cpu(i) {
 289                void *scratch = vmalloc(IPCOMP_SCRATCH_SIZE);
 290                if (!scratch)
 291                        return NULL;
 292                *per_cpu_ptr(scratches, i) = scratch;
 293        }
 294
 295        return scratches;
 296}
 297
 298static void ipcomp6_free_tfms(struct crypto_comp **tfms)
 299{
 300        struct ipcomp6_tfms *pos;
 301        int cpu;
 302
 303        list_for_each_entry(pos, &ipcomp6_tfms_list, list) {
 304                if (pos->tfms == tfms)
 305                        break;
 306        }
 307
 308        BUG_TRAP(pos);
 309
 310        if (--pos->users)
 311                return;
 312
 313        list_del(&pos->list);
 314        kfree(pos);
 315
 316        if (!tfms)
 317                return;
 318
 319        for_each_possible_cpu(cpu) {
 320                struct crypto_comp *tfm = *per_cpu_ptr(tfms, cpu);
 321                crypto_free_comp(tfm);
 322        }
 323        free_percpu(tfms);
 324}
 325
 326static struct crypto_comp **ipcomp6_alloc_tfms(const char *alg_name)
 327{
 328        struct ipcomp6_tfms *pos;
 329        struct crypto_comp **tfms;
 330        int cpu;
 331
 332        /* This can be any valid CPU ID so we don't need locking. */
 333        cpu = raw_smp_processor_id();
 334
 335        list_for_each_entry(pos, &ipcomp6_tfms_list, list) {
 336                struct crypto_comp *tfm;
 337
 338                tfms = pos->tfms;
 339                tfm = *per_cpu_ptr(tfms, cpu);
 340
 341                if (!strcmp(crypto_comp_name(tfm), alg_name)) {
 342                        pos->users++;
 343                        return tfms;
 344                }
 345        }
 346
 347        pos = kmalloc(sizeof(*pos), GFP_KERNEL);
 348        if (!pos)
 349                return NULL;
 350
 351        pos->users = 1;
 352        INIT_LIST_HEAD(&pos->list);
 353        list_add(&pos->list, &ipcomp6_tfms_list);
 354
 355        pos->tfms = tfms = alloc_percpu(struct crypto_comp *);
 356        if (!tfms)
 357                goto error;
 358
 359        for_each_possible_cpu(cpu) {
 360                struct crypto_comp *tfm = crypto_alloc_comp(alg_name, 0,
 361                                                            CRYPTO_ALG_ASYNC);
 362                if (IS_ERR(tfm))
 363                        goto error;
 364                *per_cpu_ptr(tfms, cpu) = tfm;
 365        }
 366
 367        return tfms;
 368
 369error:
 370        ipcomp6_free_tfms(tfms);
 371        return NULL;
 372}
 373
 374static void ipcomp6_free_data(struct ipcomp_data *ipcd)
 375{
 376        if (ipcd->tfms)
 377                ipcomp6_free_tfms(ipcd->tfms);
 378        ipcomp6_free_scratches();
 379}
 380
 381static void ipcomp6_destroy(struct xfrm_state *x)
 382{
 383        struct ipcomp_data *ipcd = x->data;
 384        if (!ipcd)
 385                return;
 386        xfrm_state_delete_tunnel(x);
 387        mutex_lock(&ipcomp6_resource_mutex);
 388        ipcomp6_free_data(ipcd);
 389        mutex_unlock(&ipcomp6_resource_mutex);
 390        kfree(ipcd);
 391
 392        xfrm6_tunnel_free_spi((xfrm_address_t *)&x->props.saddr);
 393}
 394
 395static int ipcomp6_init_state(struct xfrm_state *x)
 396{
 397        int err;
 398        struct ipcomp_data *ipcd;
 399        struct xfrm_algo_desc *calg_desc;
 400
 401        err = -EINVAL;
 402        if (!x->calg)
 403                goto out;
 404
 405        if (x->encap)
 406                goto out;
 407
 408        err = -ENOMEM;
 409        ipcd = kzalloc(sizeof(*ipcd), GFP_KERNEL);
 410        if (!ipcd)
 411                goto out;
 412
 413        x->props.header_len = 0;
 414        switch (x->props.mode) {
 415        case XFRM_MODE_BEET:
 416        case XFRM_MODE_TRANSPORT:
 417                break;
 418        case XFRM_MODE_TUNNEL:
 419                x->props.header_len += sizeof(struct ipv6hdr);
 420        default:
 421                goto error;
 422        }
 423
 424        mutex_lock(&ipcomp6_resource_mutex);
 425        if (!ipcomp6_alloc_scratches())
 426                goto error;
 427
 428        ipcd->tfms = ipcomp6_alloc_tfms(x->calg->alg_name);
 429        if (!ipcd->tfms)
 430                goto error;
 431        mutex_unlock(&ipcomp6_resource_mutex);
 432
 433        if (x->props.mode == XFRM_MODE_TUNNEL) {
 434                err = ipcomp6_tunnel_attach(x);
 435                if (err)
 436                        goto error_tunnel;
 437        }
 438
 439        calg_desc = xfrm_calg_get_byname(x->calg->alg_name, 0);
 440        BUG_ON(!calg_desc);
 441        ipcd->threshold = calg_desc->uinfo.comp.threshold;
 442        x->data = ipcd;
 443        err = 0;
 444out:
 445        return err;
 446error_tunnel:
 447        mutex_lock(&ipcomp6_resource_mutex);
 448error:
 449        ipcomp6_free_data(ipcd);
 450        mutex_unlock(&ipcomp6_resource_mutex);
 451        kfree(ipcd);
 452
 453        goto out;
 454}
 455
 456static struct xfrm_type ipcomp6_type =
 457{
 458        .description    = "IPCOMP6",
 459        .owner          = THIS_MODULE,
 460        .proto          = IPPROTO_COMP,
 461        .init_state     = ipcomp6_init_state,
 462        .destructor     = ipcomp6_destroy,
 463        .input          = ipcomp6_input,
 464        .output         = ipcomp6_output,
 465        .hdr_offset     = xfrm6_find_1stfragopt,
 466};
 467
 468static struct inet6_protocol ipcomp6_protocol =
 469{
 470        .handler        = xfrm6_rcv,
 471        .err_handler    = ipcomp6_err,
 472        .flags          = INET6_PROTO_NOPOLICY,
 473};
 474
 475static int __init ipcomp6_init(void)
 476{
 477        if (xfrm_register_type(&ipcomp6_type, AF_INET6) < 0) {
 478                printk(KERN_INFO "ipcomp6 init: can't add xfrm type\n");
 479                return -EAGAIN;
 480        }
 481        if (inet6_add_protocol(&ipcomp6_protocol, IPPROTO_COMP) < 0) {
 482                printk(KERN_INFO "ipcomp6 init: can't add protocol\n");
 483                xfrm_unregister_type(&ipcomp6_type, AF_INET6);
 484                return -EAGAIN;
 485        }
 486        return 0;
 487}
 488
 489static void __exit ipcomp6_fini(void)
 490{
 491        if (inet6_del_protocol(&ipcomp6_protocol, IPPROTO_COMP) < 0)
 492                printk(KERN_INFO "ipv6 ipcomp close: can't remove protocol\n");
 493        if (xfrm_unregister_type(&ipcomp6_type, AF_INET6) < 0)
 494                printk(KERN_INFO "ipv6 ipcomp close: can't remove xfrm type\n");
 495}
 496
 497module_init(ipcomp6_init);
 498module_exit(ipcomp6_fini);
 499MODULE_LICENSE("GPL");
 500MODULE_DESCRIPTION("IP Payload Compression Protocol (IPComp) for IPv6 - RFC3173");
 501MODULE_AUTHOR("Mitsuru KANDA <mk@linux-ipv6.org>");
 502
 503MODULE_ALIAS_XFRM_TYPE(AF_INET6, XFRM_PROTO_COMP);
 504