Blame view

net/xfrm/xfrm_ipcomp.c 7.66 KB
6fccab671   Herbert Xu   ipsec: ipcomp - M...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  /*
   * IP Payload Compression Protocol (IPComp) - RFC3173.
   *
   * Copyright (c) 2003 James Morris <jmorris@intercode.com.au>
   * Copyright (c) 2003-2008 Herbert Xu <herbert@gondor.apana.org.au>
   *
   * This program is free software; you can redistribute it and/or modify it
   * under the terms of the GNU General Public License as published by the Free
   * Software Foundation; either version 2 of the License, or (at your option)
   * any later version.
   *
   * Todo:
   *   - Tunable compression parameters.
   *   - Compression stats.
   *   - Adaptive compression.
   */
  
  #include <linux/crypto.h>
  #include <linux/err.h>
  #include <linux/list.h>
  #include <linux/module.h>
  #include <linux/mutex.h>
  #include <linux/percpu.h>
5a0e3ad6a   Tejun Heo   include cleanup: ...
24
  #include <linux/slab.h>
6fccab671   Herbert Xu   ipsec: ipcomp - M...
25
26
27
28
29
30
31
32
  #include <linux/smp.h>
  #include <linux/vmalloc.h>
  #include <net/ip.h>
  #include <net/ipcomp.h>
  #include <net/xfrm.h>
  
  struct ipcomp_tfms {
  	struct list_head list;
7d720c3e4   Tejun Heo   percpu: add __per...
33
  	struct crypto_comp * __percpu *tfms;
6fccab671   Herbert Xu   ipsec: ipcomp - M...
34
35
36
37
  	int users;
  };
  
  static DEFINE_MUTEX(ipcomp_resource_mutex);
7d720c3e4   Tejun Heo   percpu: add __per...
38
  static void * __percpu *ipcomp_scratches;
6fccab671   Herbert Xu   ipsec: ipcomp - M...
39
40
41
42
43
44
45
46
47
48
49
50
51
  static int ipcomp_scratch_users;
  static LIST_HEAD(ipcomp_tfms_list);
  
  static int ipcomp_decompress(struct xfrm_state *x, struct sk_buff *skb)
  {
  	struct ipcomp_data *ipcd = x->data;
  	const int plen = skb->len;
  	int dlen = IPCOMP_SCRATCH_SIZE;
  	const u8 *start = skb->data;
  	const int cpu = get_cpu();
  	u8 *scratch = *per_cpu_ptr(ipcomp_scratches, cpu);
  	struct crypto_comp *tfm = *per_cpu_ptr(ipcd->tfms, cpu);
  	int err = crypto_comp_decompress(tfm, start, plen, scratch, &dlen);
7d7e5a60c   Herbert Xu   ipsec: ipcomp - D...
52
  	int len;
6fccab671   Herbert Xu   ipsec: ipcomp - M...
53
54
55
56
57
58
59
60
  
  	if (err)
  		goto out;
  
  	if (dlen < (plen + sizeof(struct ip_comp_hdr))) {
  		err = -EINVAL;
  		goto out;
  	}
7d7e5a60c   Herbert Xu   ipsec: ipcomp - D...
61
62
63
  	len = dlen - plen;
  	if (len > skb_tailroom(skb))
  		len = skb_tailroom(skb);
7d7e5a60c   Herbert Xu   ipsec: ipcomp - D...
64
65
66
67
68
69
70
  	__skb_put(skb, len);
  
  	len += plen;
  	skb_copy_to_linear_data(skb, scratch, len);
  
  	while ((scratch += len, dlen -= len) > 0) {
  		skb_frag_t *frag;
804cf14ea   Ian Campbell   net: xfrm: conver...
71
  		struct page *page;
7d7e5a60c   Herbert Xu   ipsec: ipcomp - D...
72
73
74
75
76
77
  
  		err = -EMSGSIZE;
  		if (WARN_ON(skb_shinfo(skb)->nr_frags >= MAX_SKB_FRAGS))
  			goto out;
  
  		frag = skb_shinfo(skb)->frags + skb_shinfo(skb)->nr_frags;
804cf14ea   Ian Campbell   net: xfrm: conver...
78
  		page = alloc_page(GFP_ATOMIC);
7d7e5a60c   Herbert Xu   ipsec: ipcomp - D...
79
80
  
  		err = -ENOMEM;
804cf14ea   Ian Campbell   net: xfrm: conver...
81
  		if (!page)
7d7e5a60c   Herbert Xu   ipsec: ipcomp - D...
82
  			goto out;
804cf14ea   Ian Campbell   net: xfrm: conver...
83
  		__skb_frag_set_page(frag, page);
7d7e5a60c   Herbert Xu   ipsec: ipcomp - D...
84
85
86
  		len = PAGE_SIZE;
  		if (dlen < len)
  			len = dlen;
7d7e5a60c   Herbert Xu   ipsec: ipcomp - D...
87
  		frag->page_offset = 0;
9e903e085   Eric Dumazet   net: add skb frag...
88
  		skb_frag_size_set(frag, len);
804cf14ea   Ian Campbell   net: xfrm: conver...
89
  		memcpy(skb_frag_address(frag), scratch, len);
7d7e5a60c   Herbert Xu   ipsec: ipcomp - D...
90
91
92
93
94
95
96
97
  		skb->truesize += len;
  		skb->data_len += len;
  		skb->len += len;
  
  		skb_shinfo(skb)->nr_frags++;
  	}
  
  	err = 0;
6fccab671   Herbert Xu   ipsec: ipcomp - M...
98

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