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