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