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