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