16fccab67SHerbert Xu /* 26fccab67SHerbert Xu * IP Payload Compression Protocol (IPComp) - RFC3173. 36fccab67SHerbert Xu * 46fccab67SHerbert Xu * Copyright (c) 2003 James Morris <jmorris@intercode.com.au> 56fccab67SHerbert Xu * Copyright (c) 2003-2008 Herbert Xu <herbert@gondor.apana.org.au> 66fccab67SHerbert Xu * 76fccab67SHerbert Xu * This program is free software; you can redistribute it and/or modify it 86fccab67SHerbert Xu * under the terms of the GNU General Public License as published by the Free 96fccab67SHerbert Xu * Software Foundation; either version 2 of the License, or (at your option) 106fccab67SHerbert Xu * any later version. 116fccab67SHerbert Xu * 126fccab67SHerbert Xu * Todo: 136fccab67SHerbert Xu * - Tunable compression parameters. 146fccab67SHerbert Xu * - Compression stats. 156fccab67SHerbert Xu * - Adaptive compression. 166fccab67SHerbert Xu */ 176fccab67SHerbert Xu 186fccab67SHerbert Xu #include <linux/crypto.h> 196fccab67SHerbert Xu #include <linux/err.h> 20*7d7e5a60SHerbert Xu #include <linux/gfp.h> 216fccab67SHerbert Xu #include <linux/list.h> 226fccab67SHerbert Xu #include <linux/module.h> 236fccab67SHerbert Xu #include <linux/mutex.h> 246fccab67SHerbert Xu #include <linux/percpu.h> 256fccab67SHerbert Xu #include <linux/rtnetlink.h> 266fccab67SHerbert Xu #include <linux/smp.h> 276fccab67SHerbert Xu #include <linux/vmalloc.h> 286fccab67SHerbert Xu #include <net/ip.h> 296fccab67SHerbert Xu #include <net/ipcomp.h> 306fccab67SHerbert Xu #include <net/xfrm.h> 316fccab67SHerbert Xu 326fccab67SHerbert Xu struct ipcomp_tfms { 336fccab67SHerbert Xu struct list_head list; 346fccab67SHerbert Xu struct crypto_comp **tfms; 356fccab67SHerbert Xu int users; 366fccab67SHerbert Xu }; 376fccab67SHerbert Xu 386fccab67SHerbert Xu static DEFINE_MUTEX(ipcomp_resource_mutex); 396fccab67SHerbert Xu static void **ipcomp_scratches; 406fccab67SHerbert Xu static int ipcomp_scratch_users; 416fccab67SHerbert Xu static LIST_HEAD(ipcomp_tfms_list); 426fccab67SHerbert Xu 436fccab67SHerbert Xu static int ipcomp_decompress(struct xfrm_state *x, struct sk_buff *skb) 446fccab67SHerbert Xu { 456fccab67SHerbert Xu struct ipcomp_data *ipcd = x->data; 466fccab67SHerbert Xu const int plen = skb->len; 476fccab67SHerbert Xu int dlen = IPCOMP_SCRATCH_SIZE; 486fccab67SHerbert Xu const u8 *start = skb->data; 496fccab67SHerbert Xu const int cpu = get_cpu(); 506fccab67SHerbert Xu u8 *scratch = *per_cpu_ptr(ipcomp_scratches, cpu); 516fccab67SHerbert Xu struct crypto_comp *tfm = *per_cpu_ptr(ipcd->tfms, cpu); 526fccab67SHerbert Xu int err = crypto_comp_decompress(tfm, start, plen, scratch, &dlen); 53*7d7e5a60SHerbert Xu int len; 546fccab67SHerbert Xu 556fccab67SHerbert Xu if (err) 566fccab67SHerbert Xu goto out; 576fccab67SHerbert Xu 586fccab67SHerbert Xu if (dlen < (plen + sizeof(struct ip_comp_hdr))) { 596fccab67SHerbert Xu err = -EINVAL; 606fccab67SHerbert Xu goto out; 616fccab67SHerbert Xu } 626fccab67SHerbert Xu 63*7d7e5a60SHerbert Xu len = dlen - plen; 64*7d7e5a60SHerbert Xu if (len > skb_tailroom(skb)) 65*7d7e5a60SHerbert Xu len = skb_tailroom(skb); 66*7d7e5a60SHerbert Xu 67*7d7e5a60SHerbert Xu skb->truesize += len; 68*7d7e5a60SHerbert Xu __skb_put(skb, len); 69*7d7e5a60SHerbert Xu 70*7d7e5a60SHerbert Xu len += plen; 71*7d7e5a60SHerbert Xu skb_copy_to_linear_data(skb, scratch, len); 72*7d7e5a60SHerbert Xu 73*7d7e5a60SHerbert Xu while ((scratch += len, dlen -= len) > 0) { 74*7d7e5a60SHerbert Xu skb_frag_t *frag; 75*7d7e5a60SHerbert Xu 76*7d7e5a60SHerbert Xu err = -EMSGSIZE; 77*7d7e5a60SHerbert Xu if (WARN_ON(skb_shinfo(skb)->nr_frags >= MAX_SKB_FRAGS)) 786fccab67SHerbert Xu goto out; 796fccab67SHerbert Xu 80*7d7e5a60SHerbert Xu frag = skb_shinfo(skb)->frags + skb_shinfo(skb)->nr_frags; 81*7d7e5a60SHerbert Xu frag->page = alloc_page(GFP_ATOMIC); 82*7d7e5a60SHerbert Xu 83*7d7e5a60SHerbert Xu err = -ENOMEM; 84*7d7e5a60SHerbert Xu if (!frag->page) 85*7d7e5a60SHerbert Xu goto out; 86*7d7e5a60SHerbert Xu 87*7d7e5a60SHerbert Xu len = PAGE_SIZE; 88*7d7e5a60SHerbert Xu if (dlen < len) 89*7d7e5a60SHerbert Xu len = dlen; 90*7d7e5a60SHerbert Xu 91*7d7e5a60SHerbert Xu memcpy(page_address(frag->page), scratch, len); 92*7d7e5a60SHerbert Xu 93*7d7e5a60SHerbert Xu frag->page_offset = 0; 94*7d7e5a60SHerbert Xu frag->size = len; 95*7d7e5a60SHerbert Xu skb->truesize += len; 96*7d7e5a60SHerbert Xu skb->data_len += len; 97*7d7e5a60SHerbert Xu skb->len += len; 98*7d7e5a60SHerbert Xu 99*7d7e5a60SHerbert Xu skb_shinfo(skb)->nr_frags++; 100*7d7e5a60SHerbert Xu } 101*7d7e5a60SHerbert Xu 102*7d7e5a60SHerbert Xu err = 0; 103*7d7e5a60SHerbert Xu 1046fccab67SHerbert Xu out: 1056fccab67SHerbert Xu put_cpu(); 1066fccab67SHerbert Xu return err; 1076fccab67SHerbert Xu } 1086fccab67SHerbert Xu 1096fccab67SHerbert Xu int ipcomp_input(struct xfrm_state *x, struct sk_buff *skb) 1106fccab67SHerbert Xu { 1116fccab67SHerbert Xu int nexthdr; 1126fccab67SHerbert Xu int err = -ENOMEM; 1136fccab67SHerbert Xu struct ip_comp_hdr *ipch; 1146fccab67SHerbert Xu 1156fccab67SHerbert Xu if (skb_linearize_cow(skb)) 1166fccab67SHerbert Xu goto out; 1176fccab67SHerbert Xu 1186fccab67SHerbert Xu skb->ip_summed = CHECKSUM_NONE; 1196fccab67SHerbert Xu 1206fccab67SHerbert Xu /* Remove ipcomp header and decompress original payload */ 1216fccab67SHerbert Xu ipch = (void *)skb->data; 1226fccab67SHerbert Xu nexthdr = ipch->nexthdr; 1236fccab67SHerbert Xu 1246fccab67SHerbert Xu skb->transport_header = skb->network_header + sizeof(*ipch); 1256fccab67SHerbert Xu __skb_pull(skb, sizeof(*ipch)); 1266fccab67SHerbert Xu err = ipcomp_decompress(x, skb); 1276fccab67SHerbert Xu if (err) 1286fccab67SHerbert Xu goto out; 1296fccab67SHerbert Xu 1306fccab67SHerbert Xu err = nexthdr; 1316fccab67SHerbert Xu 1326fccab67SHerbert Xu out: 1336fccab67SHerbert Xu return err; 1346fccab67SHerbert Xu } 1356fccab67SHerbert Xu EXPORT_SYMBOL_GPL(ipcomp_input); 1366fccab67SHerbert Xu 1376fccab67SHerbert Xu static int ipcomp_compress(struct xfrm_state *x, struct sk_buff *skb) 1386fccab67SHerbert Xu { 1396fccab67SHerbert Xu struct ipcomp_data *ipcd = x->data; 1406fccab67SHerbert Xu const int plen = skb->len; 1416fccab67SHerbert Xu int dlen = IPCOMP_SCRATCH_SIZE; 1426fccab67SHerbert Xu u8 *start = skb->data; 1436fccab67SHerbert Xu const int cpu = get_cpu(); 1446fccab67SHerbert Xu u8 *scratch = *per_cpu_ptr(ipcomp_scratches, cpu); 1456fccab67SHerbert Xu struct crypto_comp *tfm = *per_cpu_ptr(ipcd->tfms, cpu); 1466fccab67SHerbert Xu int err; 1476fccab67SHerbert Xu 1486fccab67SHerbert Xu local_bh_disable(); 1496fccab67SHerbert Xu err = crypto_comp_compress(tfm, start, plen, scratch, &dlen); 1506fccab67SHerbert Xu local_bh_enable(); 1516fccab67SHerbert Xu if (err) 1526fccab67SHerbert Xu goto out; 1536fccab67SHerbert Xu 1546fccab67SHerbert Xu if ((dlen + sizeof(struct ip_comp_hdr)) >= plen) { 1556fccab67SHerbert Xu err = -EMSGSIZE; 1566fccab67SHerbert Xu goto out; 1576fccab67SHerbert Xu } 1586fccab67SHerbert Xu 1596fccab67SHerbert Xu memcpy(start + sizeof(struct ip_comp_hdr), scratch, dlen); 1606fccab67SHerbert Xu put_cpu(); 1616fccab67SHerbert Xu 1626fccab67SHerbert Xu pskb_trim(skb, dlen + sizeof(struct ip_comp_hdr)); 1636fccab67SHerbert Xu return 0; 1646fccab67SHerbert Xu 1656fccab67SHerbert Xu out: 1666fccab67SHerbert Xu put_cpu(); 1676fccab67SHerbert Xu return err; 1686fccab67SHerbert Xu } 1696fccab67SHerbert Xu 1706fccab67SHerbert Xu int ipcomp_output(struct xfrm_state *x, struct sk_buff *skb) 1716fccab67SHerbert Xu { 1726fccab67SHerbert Xu int err; 1736fccab67SHerbert Xu struct ip_comp_hdr *ipch; 1746fccab67SHerbert Xu struct ipcomp_data *ipcd = x->data; 1756fccab67SHerbert Xu 1766fccab67SHerbert Xu if (skb->len < ipcd->threshold) { 1776fccab67SHerbert Xu /* Don't bother compressing */ 1786fccab67SHerbert Xu goto out_ok; 1796fccab67SHerbert Xu } 1806fccab67SHerbert Xu 1816fccab67SHerbert Xu if (skb_linearize_cow(skb)) 1826fccab67SHerbert Xu goto out_ok; 1836fccab67SHerbert Xu 1846fccab67SHerbert Xu err = ipcomp_compress(x, skb); 1856fccab67SHerbert Xu 1866fccab67SHerbert Xu if (err) { 1876fccab67SHerbert Xu goto out_ok; 1886fccab67SHerbert Xu } 1896fccab67SHerbert Xu 1906fccab67SHerbert Xu /* Install ipcomp header, convert into ipcomp datagram. */ 1916fccab67SHerbert Xu ipch = ip_comp_hdr(skb); 1926fccab67SHerbert Xu ipch->nexthdr = *skb_mac_header(skb); 1936fccab67SHerbert Xu ipch->flags = 0; 1946fccab67SHerbert Xu ipch->cpi = htons((u16 )ntohl(x->id.spi)); 1956fccab67SHerbert Xu *skb_mac_header(skb) = IPPROTO_COMP; 1966fccab67SHerbert Xu out_ok: 1976fccab67SHerbert Xu skb_push(skb, -skb_network_offset(skb)); 1986fccab67SHerbert Xu return 0; 1996fccab67SHerbert Xu } 2006fccab67SHerbert Xu EXPORT_SYMBOL_GPL(ipcomp_output); 2016fccab67SHerbert Xu 2026fccab67SHerbert Xu static void ipcomp_free_scratches(void) 2036fccab67SHerbert Xu { 2046fccab67SHerbert Xu int i; 2056fccab67SHerbert Xu void **scratches; 2066fccab67SHerbert Xu 2076fccab67SHerbert Xu if (--ipcomp_scratch_users) 2086fccab67SHerbert Xu return; 2096fccab67SHerbert Xu 2106fccab67SHerbert Xu scratches = ipcomp_scratches; 2116fccab67SHerbert Xu if (!scratches) 2126fccab67SHerbert Xu return; 2136fccab67SHerbert Xu 2146fccab67SHerbert Xu for_each_possible_cpu(i) 2156fccab67SHerbert Xu vfree(*per_cpu_ptr(scratches, i)); 2166fccab67SHerbert Xu 2176fccab67SHerbert Xu free_percpu(scratches); 2186fccab67SHerbert Xu } 2196fccab67SHerbert Xu 2206fccab67SHerbert Xu static void **ipcomp_alloc_scratches(void) 2216fccab67SHerbert Xu { 2226fccab67SHerbert Xu int i; 2236fccab67SHerbert Xu void **scratches; 2246fccab67SHerbert Xu 2256fccab67SHerbert Xu if (ipcomp_scratch_users++) 2266fccab67SHerbert Xu return ipcomp_scratches; 2276fccab67SHerbert Xu 2286fccab67SHerbert Xu scratches = alloc_percpu(void *); 2296fccab67SHerbert Xu if (!scratches) 2306fccab67SHerbert Xu return NULL; 2316fccab67SHerbert Xu 2326fccab67SHerbert Xu ipcomp_scratches = scratches; 2336fccab67SHerbert Xu 2346fccab67SHerbert Xu for_each_possible_cpu(i) { 2356fccab67SHerbert Xu void *scratch = vmalloc(IPCOMP_SCRATCH_SIZE); 2366fccab67SHerbert Xu if (!scratch) 2376fccab67SHerbert Xu return NULL; 2386fccab67SHerbert Xu *per_cpu_ptr(scratches, i) = scratch; 2396fccab67SHerbert Xu } 2406fccab67SHerbert Xu 2416fccab67SHerbert Xu return scratches; 2426fccab67SHerbert Xu } 2436fccab67SHerbert Xu 2446fccab67SHerbert Xu static void ipcomp_free_tfms(struct crypto_comp **tfms) 2456fccab67SHerbert Xu { 2466fccab67SHerbert Xu struct ipcomp_tfms *pos; 2476fccab67SHerbert Xu int cpu; 2486fccab67SHerbert Xu 2496fccab67SHerbert Xu list_for_each_entry(pos, &ipcomp_tfms_list, list) { 2506fccab67SHerbert Xu if (pos->tfms == tfms) 2516fccab67SHerbert Xu break; 2526fccab67SHerbert Xu } 2536fccab67SHerbert Xu 2546fccab67SHerbert Xu BUG_TRAP(pos); 2556fccab67SHerbert Xu 2566fccab67SHerbert Xu if (--pos->users) 2576fccab67SHerbert Xu return; 2586fccab67SHerbert Xu 2596fccab67SHerbert Xu list_del(&pos->list); 2606fccab67SHerbert Xu kfree(pos); 2616fccab67SHerbert Xu 2626fccab67SHerbert Xu if (!tfms) 2636fccab67SHerbert Xu return; 2646fccab67SHerbert Xu 2656fccab67SHerbert Xu for_each_possible_cpu(cpu) { 2666fccab67SHerbert Xu struct crypto_comp *tfm = *per_cpu_ptr(tfms, cpu); 2676fccab67SHerbert Xu crypto_free_comp(tfm); 2686fccab67SHerbert Xu } 2696fccab67SHerbert Xu free_percpu(tfms); 2706fccab67SHerbert Xu } 2716fccab67SHerbert Xu 2726fccab67SHerbert Xu static struct crypto_comp **ipcomp_alloc_tfms(const char *alg_name) 2736fccab67SHerbert Xu { 2746fccab67SHerbert Xu struct ipcomp_tfms *pos; 2756fccab67SHerbert Xu struct crypto_comp **tfms; 2766fccab67SHerbert Xu int cpu; 2776fccab67SHerbert Xu 2786fccab67SHerbert Xu /* This can be any valid CPU ID so we don't need locking. */ 2796fccab67SHerbert Xu cpu = raw_smp_processor_id(); 2806fccab67SHerbert Xu 2816fccab67SHerbert Xu list_for_each_entry(pos, &ipcomp_tfms_list, list) { 2826fccab67SHerbert Xu struct crypto_comp *tfm; 2836fccab67SHerbert Xu 2846fccab67SHerbert Xu tfms = pos->tfms; 2856fccab67SHerbert Xu tfm = *per_cpu_ptr(tfms, cpu); 2866fccab67SHerbert Xu 2876fccab67SHerbert Xu if (!strcmp(crypto_comp_name(tfm), alg_name)) { 2886fccab67SHerbert Xu pos->users++; 2896fccab67SHerbert Xu return tfms; 2906fccab67SHerbert Xu } 2916fccab67SHerbert Xu } 2926fccab67SHerbert Xu 2936fccab67SHerbert Xu pos = kmalloc(sizeof(*pos), GFP_KERNEL); 2946fccab67SHerbert Xu if (!pos) 2956fccab67SHerbert Xu return NULL; 2966fccab67SHerbert Xu 2976fccab67SHerbert Xu pos->users = 1; 2986fccab67SHerbert Xu INIT_LIST_HEAD(&pos->list); 2996fccab67SHerbert Xu list_add(&pos->list, &ipcomp_tfms_list); 3006fccab67SHerbert Xu 3016fccab67SHerbert Xu pos->tfms = tfms = alloc_percpu(struct crypto_comp *); 3026fccab67SHerbert Xu if (!tfms) 3036fccab67SHerbert Xu goto error; 3046fccab67SHerbert Xu 3056fccab67SHerbert Xu for_each_possible_cpu(cpu) { 3066fccab67SHerbert Xu struct crypto_comp *tfm = crypto_alloc_comp(alg_name, 0, 3076fccab67SHerbert Xu CRYPTO_ALG_ASYNC); 3086fccab67SHerbert Xu if (IS_ERR(tfm)) 3096fccab67SHerbert Xu goto error; 3106fccab67SHerbert Xu *per_cpu_ptr(tfms, cpu) = tfm; 3116fccab67SHerbert Xu } 3126fccab67SHerbert Xu 3136fccab67SHerbert Xu return tfms; 3146fccab67SHerbert Xu 3156fccab67SHerbert Xu error: 3166fccab67SHerbert Xu ipcomp_free_tfms(tfms); 3176fccab67SHerbert Xu return NULL; 3186fccab67SHerbert Xu } 3196fccab67SHerbert Xu 3206fccab67SHerbert Xu static void ipcomp_free_data(struct ipcomp_data *ipcd) 3216fccab67SHerbert Xu { 3226fccab67SHerbert Xu if (ipcd->tfms) 3236fccab67SHerbert Xu ipcomp_free_tfms(ipcd->tfms); 3246fccab67SHerbert Xu ipcomp_free_scratches(); 3256fccab67SHerbert Xu } 3266fccab67SHerbert Xu 3276fccab67SHerbert Xu void ipcomp_destroy(struct xfrm_state *x) 3286fccab67SHerbert Xu { 3296fccab67SHerbert Xu struct ipcomp_data *ipcd = x->data; 3306fccab67SHerbert Xu if (!ipcd) 3316fccab67SHerbert Xu return; 3326fccab67SHerbert Xu xfrm_state_delete_tunnel(x); 3336fccab67SHerbert Xu mutex_lock(&ipcomp_resource_mutex); 3346fccab67SHerbert Xu ipcomp_free_data(ipcd); 3356fccab67SHerbert Xu mutex_unlock(&ipcomp_resource_mutex); 3366fccab67SHerbert Xu kfree(ipcd); 3376fccab67SHerbert Xu } 3386fccab67SHerbert Xu EXPORT_SYMBOL_GPL(ipcomp_destroy); 3396fccab67SHerbert Xu 3406fccab67SHerbert Xu int ipcomp_init_state(struct xfrm_state *x) 3416fccab67SHerbert Xu { 3426fccab67SHerbert Xu int err; 3436fccab67SHerbert Xu struct ipcomp_data *ipcd; 3446fccab67SHerbert Xu struct xfrm_algo_desc *calg_desc; 3456fccab67SHerbert Xu 3466fccab67SHerbert Xu err = -EINVAL; 3476fccab67SHerbert Xu if (!x->calg) 3486fccab67SHerbert Xu goto out; 3496fccab67SHerbert Xu 3506fccab67SHerbert Xu if (x->encap) 3516fccab67SHerbert Xu goto out; 3526fccab67SHerbert Xu 3536fccab67SHerbert Xu err = -ENOMEM; 3546fccab67SHerbert Xu ipcd = kzalloc(sizeof(*ipcd), GFP_KERNEL); 3556fccab67SHerbert Xu if (!ipcd) 3566fccab67SHerbert Xu goto out; 3576fccab67SHerbert Xu 3586fccab67SHerbert Xu mutex_lock(&ipcomp_resource_mutex); 3596fccab67SHerbert Xu if (!ipcomp_alloc_scratches()) 3606fccab67SHerbert Xu goto error; 3616fccab67SHerbert Xu 3626fccab67SHerbert Xu ipcd->tfms = ipcomp_alloc_tfms(x->calg->alg_name); 3636fccab67SHerbert Xu if (!ipcd->tfms) 3646fccab67SHerbert Xu goto error; 3656fccab67SHerbert Xu mutex_unlock(&ipcomp_resource_mutex); 3666fccab67SHerbert Xu 3676fccab67SHerbert Xu calg_desc = xfrm_calg_get_byname(x->calg->alg_name, 0); 3686fccab67SHerbert Xu BUG_ON(!calg_desc); 3696fccab67SHerbert Xu ipcd->threshold = calg_desc->uinfo.comp.threshold; 3706fccab67SHerbert Xu x->data = ipcd; 3716fccab67SHerbert Xu err = 0; 3726fccab67SHerbert Xu out: 3736fccab67SHerbert Xu return err; 3746fccab67SHerbert Xu 3756fccab67SHerbert Xu error: 3766fccab67SHerbert Xu ipcomp_free_data(ipcd); 3776fccab67SHerbert Xu mutex_unlock(&ipcomp_resource_mutex); 3786fccab67SHerbert Xu kfree(ipcd); 3796fccab67SHerbert Xu goto out; 3806fccab67SHerbert Xu } 3816fccab67SHerbert Xu EXPORT_SYMBOL_GPL(ipcomp_init_state); 3826fccab67SHerbert Xu 3836fccab67SHerbert Xu MODULE_LICENSE("GPL"); 3846fccab67SHerbert Xu MODULE_DESCRIPTION("IP Payload Compression Protocol (IPComp) - RFC3173"); 3856fccab67SHerbert Xu MODULE_AUTHOR("James Morris <jmorris@intercode.com.au>"); 386