Blame view

net/xfrm/xfrm_ipcomp.c 7.43 KB
2874c5fd2   Thomas Gleixner   treewide: Replace...
1
  // SPDX-License-Identifier: GPL-2.0-or-later
6fccab671   Herbert Xu   ipsec: ipcomp - M...
2
3
4
5
6
7
  /*
   * 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>
   *
6fccab671   Herbert Xu   ipsec: ipcomp - M...
8
9
10
11
12
13
14
15
16
17
18
19
   * 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: ...
20
  #include <linux/slab.h>
6fccab671   Herbert Xu   ipsec: ipcomp - M...
21
22
23
24
25
26
27
28
  #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...
29
  	struct crypto_comp * __percpu *tfms;
6fccab671   Herbert Xu   ipsec: ipcomp - M...
30
31
32
33
  	int users;
  };
  
  static DEFINE_MUTEX(ipcomp_resource_mutex);
7d720c3e4   Tejun Heo   percpu: add __per...
34
  static void * __percpu *ipcomp_scratches;
6fccab671   Herbert Xu   ipsec: ipcomp - M...
35
36
37
38
39
40
41
42
43
44
45
46
47
  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...
48
  	int len;
6fccab671   Herbert Xu   ipsec: ipcomp - M...
49
50
51
52
53
54
55
56
  
  	if (err)
  		goto out;
  
  	if (dlen < (plen + sizeof(struct ip_comp_hdr))) {
  		err = -EINVAL;
  		goto out;
  	}
7d7e5a60c   Herbert Xu   ipsec: ipcomp - D...
57
58
59
  	len = dlen - plen;
  	if (len > skb_tailroom(skb))
  		len = skb_tailroom(skb);
7d7e5a60c   Herbert Xu   ipsec: ipcomp - D...
60
61
62
63
64
65
66
  	__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...
67
  		struct page *page;
7d7e5a60c   Herbert Xu   ipsec: ipcomp - D...
68
69
70
71
72
73
  
  		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...
74
  		page = alloc_page(GFP_ATOMIC);
7d7e5a60c   Herbert Xu   ipsec: ipcomp - D...
75
76
  
  		err = -ENOMEM;
804cf14ea   Ian Campbell   net: xfrm: conver...
77
  		if (!page)
7d7e5a60c   Herbert Xu   ipsec: ipcomp - D...
78
  			goto out;
804cf14ea   Ian Campbell   net: xfrm: conver...
79
  		__skb_frag_set_page(frag, page);
7d7e5a60c   Herbert Xu   ipsec: ipcomp - D...
80
81
82
  		len = PAGE_SIZE;
  		if (dlen < len)
  			len = dlen;
b54c9d5bd   Jonathan Lemon   net: Use skb_frag...
83
  		skb_frag_off_set(frag, 0);
9e903e085   Eric Dumazet   net: add skb frag...
84
  		skb_frag_size_set(frag, len);
804cf14ea   Ian Campbell   net: xfrm: conver...
85
  		memcpy(skb_frag_address(frag), scratch, len);
7d7e5a60c   Herbert Xu   ipsec: ipcomp - D...
86
87
88
89
90
91
92
93
  		skb->truesize += len;
  		skb->data_len += len;
  		skb->len += len;
  
  		skb_shinfo(skb)->nr_frags++;
  	}
  
  	err = 0;
6fccab671   Herbert Xu   ipsec: ipcomp - M...
94

6fccab671   Herbert Xu   ipsec: ipcomp - M...
95
96
97
98
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
  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;
12e359469   Michal Kubecek   xfrm: prevent ipc...
134
135
  	struct crypto_comp *tfm;
  	u8 *scratch;
6fccab671   Herbert Xu   ipsec: ipcomp - M...
136
137
138
  	int err;
  
  	local_bh_disable();
12e359469   Michal Kubecek   xfrm: prevent ipc...
139
140
  	scratch = *this_cpu_ptr(ipcomp_scratches);
  	tfm = *this_cpu_ptr(ipcd->tfms);
6fccab671   Herbert Xu   ipsec: ipcomp - M...
141
  	err = crypto_comp_compress(tfm, start, plen, scratch, &dlen);
6fccab671   Herbert Xu   ipsec: ipcomp - M...
142
143
144
145
146
147
148
149
150
  	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);
12e359469   Michal Kubecek   xfrm: prevent ipc...
151
  	local_bh_enable();
6fccab671   Herbert Xu   ipsec: ipcomp - M...
152
153
154
155
156
  
  	pskb_trim(skb, dlen + sizeof(struct ip_comp_hdr));
  	return 0;
  
  out:
12e359469   Michal Kubecek   xfrm: prevent ipc...
157
  	local_bh_enable();
6fccab671   Herbert Xu   ipsec: ipcomp - M...
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
  	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...
196
  	void * __percpu *scratches;
6fccab671   Herbert Xu   ipsec: ipcomp - M...
197
198
199
200
201
202
203
204
205
206
207
208
209
  
  	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...
210
  static void * __percpu *ipcomp_alloc_scratches(void)
6fccab671   Herbert Xu   ipsec: ipcomp - M...
211
  {
7d720c3e4   Tejun Heo   percpu: add __per...
212
  	void * __percpu *scratches;
5cf4eb54c   Eric Dumazet   xfrm: use vmalloc...
213
  	int i;
6fccab671   Herbert Xu   ipsec: ipcomp - M...
214
215
216
217
218
219
220
221
222
223
224
  
  	if (ipcomp_scratch_users++)
  		return ipcomp_scratches;
  
  	scratches = alloc_percpu(void *);
  	if (!scratches)
  		return NULL;
  
  	ipcomp_scratches = scratches;
  
  	for_each_possible_cpu(i) {
5cf4eb54c   Eric Dumazet   xfrm: use vmalloc...
225
226
227
  		void *scratch;
  
  		scratch = vmalloc_node(IPCOMP_SCRATCH_SIZE, cpu_to_node(i));
6fccab671   Herbert Xu   ipsec: ipcomp - M...
228
229
230
231
232
233
234
  		if (!scratch)
  			return NULL;
  		*per_cpu_ptr(scratches, i) = scratch;
  	}
  
  	return scratches;
  }
7d720c3e4   Tejun Heo   percpu: add __per...
235
  static void ipcomp_free_tfms(struct crypto_comp * __percpu *tfms)
6fccab671   Herbert Xu   ipsec: ipcomp - M...
236
237
238
239
240
241
242
243
  {
  	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_...
244
  	WARN_ON(!pos);
6fccab671   Herbert Xu   ipsec: ipcomp - M...
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
  
  	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...
261
  static struct crypto_comp * __percpu *ipcomp_alloc_tfms(const char *alg_name)
6fccab671   Herbert Xu   ipsec: ipcomp - M...
262
263
  {
  	struct ipcomp_tfms *pos;
7d720c3e4   Tejun Heo   percpu: add __per...
264
  	struct crypto_comp * __percpu *tfms;
6fccab671   Herbert Xu   ipsec: ipcomp - M...
265
  	int cpu;
6fccab671   Herbert Xu   ipsec: ipcomp - M...
266
267
268
  
  	list_for_each_entry(pos, &ipcomp_tfms_list, list) {
  		struct crypto_comp *tfm;
f7c83bcbf   Shan Wei   net: xfrm: use __...
269
  		/* This can be any valid CPU ID so we don't need locking. */
0dcd78760   Greg Hackmann   net: xfrm: use pr...
270
  		tfm = this_cpu_read(*pos->tfms);
6fccab671   Herbert Xu   ipsec: ipcomp - M...
271
272
273
  
  		if (!strcmp(crypto_comp_name(tfm), alg_name)) {
  			pos->users++;
f7c83bcbf   Shan Wei   net: xfrm: use __...
274
  			return pos->tfms;
6fccab671   Herbert Xu   ipsec: ipcomp - M...
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
  		}
  	}
  
  	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>");