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