xref: /linux/net/ipv6/ipcomp6.c (revision 98366c20a275e957416e9516db5dcb7195b4e101)
1 /*
2  * IP Payload Compression Protocol (IPComp) for IPv6 - RFC3173
3  *
4  * Copyright (C)2003 USAGI/WIDE Project
5  *
6  * Author	Mitsuru KANDA  <mk@linux-ipv6.org>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22 /*
23  * [Memo]
24  *
25  * Outbound:
26  *  The compression of IP datagram MUST be done before AH/ESP processing,
27  *  fragmentation, and the addition of Hop-by-Hop/Routing header.
28  *
29  * Inbound:
30  *  The decompression of IP datagram MUST be done after the reassembly,
31  *  AH/ESP processing.
32  */
33 #include <linux/module.h>
34 #include <net/ip.h>
35 #include <net/xfrm.h>
36 #include <net/ipcomp.h>
37 #include <asm/semaphore.h>
38 #include <linux/crypto.h>
39 #include <linux/pfkeyv2.h>
40 #include <linux/random.h>
41 #include <linux/percpu.h>
42 #include <linux/smp.h>
43 #include <linux/list.h>
44 #include <linux/vmalloc.h>
45 #include <linux/rtnetlink.h>
46 #include <net/icmp.h>
47 #include <net/ipv6.h>
48 #include <net/protocol.h>
49 #include <linux/ipv6.h>
50 #include <linux/icmpv6.h>
51 #include <linux/mutex.h>
52 
53 struct ipcomp6_tfms {
54 	struct list_head list;
55 	struct crypto_comp **tfms;
56 	int users;
57 };
58 
59 static DEFINE_MUTEX(ipcomp6_resource_mutex);
60 static void **ipcomp6_scratches;
61 static int ipcomp6_scratch_users;
62 static LIST_HEAD(ipcomp6_tfms_list);
63 
64 static int ipcomp6_input(struct xfrm_state *x, struct sk_buff *skb)
65 {
66 	int err = -ENOMEM;
67 	struct ip_comp_hdr *ipch;
68 	int plen, dlen;
69 	struct ipcomp_data *ipcd = x->data;
70 	u8 *start, *scratch;
71 	struct crypto_comp *tfm;
72 	int cpu;
73 
74 	if (skb_linearize_cow(skb))
75 		goto out;
76 
77 	skb->ip_summed = CHECKSUM_NONE;
78 
79 	/* Remove ipcomp header and decompress original payload */
80 	ipch = (void *)skb->data;
81 	skb->transport_header = skb->network_header + sizeof(*ipch);
82 	__skb_pull(skb, sizeof(*ipch));
83 
84 	/* decompression */
85 	plen = skb->len;
86 	dlen = IPCOMP_SCRATCH_SIZE;
87 	start = skb->data;
88 
89 	cpu = get_cpu();
90 	scratch = *per_cpu_ptr(ipcomp6_scratches, cpu);
91 	tfm = *per_cpu_ptr(ipcd->tfms, cpu);
92 
93 	err = crypto_comp_decompress(tfm, start, plen, scratch, &dlen);
94 	if (err)
95 		goto out_put_cpu;
96 
97 	if (dlen < (plen + sizeof(*ipch))) {
98 		err = -EINVAL;
99 		goto out_put_cpu;
100 	}
101 
102 	err = pskb_expand_head(skb, 0, dlen - plen, GFP_ATOMIC);
103 	if (err) {
104 		goto out_put_cpu;
105 	}
106 
107 	skb->truesize += dlen - plen;
108 	__skb_put(skb, dlen - plen);
109 	skb_copy_to_linear_data(skb, scratch, dlen);
110 	err = ipch->nexthdr;
111 
112 out_put_cpu:
113 	put_cpu();
114 out:
115 	return err;
116 }
117 
118 static int ipcomp6_output(struct xfrm_state *x, struct sk_buff *skb)
119 {
120 	int err;
121 	struct ip_comp_hdr *ipch;
122 	struct ipcomp_data *ipcd = x->data;
123 	int plen, dlen;
124 	u8 *start, *scratch;
125 	struct crypto_comp *tfm;
126 	int cpu;
127 
128 	/* check whether datagram len is larger than threshold */
129 	if (skb->len < ipcd->threshold) {
130 		goto out_ok;
131 	}
132 
133 	if (skb_linearize_cow(skb))
134 		goto out_ok;
135 
136 	/* compression */
137 	plen = skb->len;
138 	dlen = IPCOMP_SCRATCH_SIZE;
139 	start = skb->data;
140 
141 	cpu = get_cpu();
142 	scratch = *per_cpu_ptr(ipcomp6_scratches, cpu);
143 	tfm = *per_cpu_ptr(ipcd->tfms, cpu);
144 
145 	err = crypto_comp_compress(tfm, start, plen, scratch, &dlen);
146 	if (err || (dlen + sizeof(*ipch)) >= plen) {
147 		put_cpu();
148 		goto out_ok;
149 	}
150 	memcpy(start + sizeof(struct ip_comp_hdr), scratch, dlen);
151 	put_cpu();
152 	pskb_trim(skb, dlen + sizeof(struct ip_comp_hdr));
153 
154 	/* insert ipcomp header and replace 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 
161 out_ok:
162 	skb_push(skb, -skb_network_offset(skb));
163 
164 	return 0;
165 }
166 
167 static void ipcomp6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
168 				int type, int code, int offset, __be32 info)
169 {
170 	__be32 spi;
171 	struct ipv6hdr *iph = (struct ipv6hdr*)skb->data;
172 	struct ip_comp_hdr *ipcomph =
173 		(struct ip_comp_hdr *)(skb->data + offset);
174 	struct xfrm_state *x;
175 
176 	if (type != ICMPV6_DEST_UNREACH && type != ICMPV6_PKT_TOOBIG)
177 		return;
178 
179 	spi = htonl(ntohs(ipcomph->cpi));
180 	x = xfrm_state_lookup((xfrm_address_t *)&iph->daddr, spi, IPPROTO_COMP, AF_INET6);
181 	if (!x)
182 		return;
183 
184 	printk(KERN_DEBUG "pmtu discovery on SA IPCOMP/%08x/" NIP6_FMT "\n",
185 			spi, NIP6(iph->daddr));
186 	xfrm_state_put(x);
187 }
188 
189 static struct xfrm_state *ipcomp6_tunnel_create(struct xfrm_state *x)
190 {
191 	struct xfrm_state *t = NULL;
192 	u8 mode = XFRM_MODE_TUNNEL;
193 
194 	t = xfrm_state_alloc();
195 	if (!t)
196 		goto out;
197 
198 	t->id.proto = IPPROTO_IPV6;
199 	t->id.spi = xfrm6_tunnel_alloc_spi((xfrm_address_t *)&x->props.saddr);
200 	if (!t->id.spi)
201 		goto error;
202 
203 	memcpy(t->id.daddr.a6, x->id.daddr.a6, sizeof(struct in6_addr));
204 	memcpy(&t->sel, &x->sel, sizeof(t->sel));
205 	t->props.family = AF_INET6;
206 	if (x->props.mode == XFRM_MODE_BEET)
207 		mode = x->props.mode;
208 	t->props.mode = mode;
209 	memcpy(t->props.saddr.a6, x->props.saddr.a6, sizeof(struct in6_addr));
210 
211 	if (xfrm_init_state(t))
212 		goto error;
213 
214 	atomic_set(&t->tunnel_users, 1);
215 
216 out:
217 	return t;
218 
219 error:
220 	t->km.state = XFRM_STATE_DEAD;
221 	xfrm_state_put(t);
222 	t = NULL;
223 	goto out;
224 }
225 
226 static int ipcomp6_tunnel_attach(struct xfrm_state *x)
227 {
228 	int err = 0;
229 	struct xfrm_state *t = NULL;
230 	__be32 spi;
231 
232 	spi = xfrm6_tunnel_spi_lookup((xfrm_address_t *)&x->props.saddr);
233 	if (spi)
234 		t = xfrm_state_lookup((xfrm_address_t *)&x->id.daddr,
235 					      spi, IPPROTO_IPV6, AF_INET6);
236 	if (!t) {
237 		t = ipcomp6_tunnel_create(x);
238 		if (!t) {
239 			err = -EINVAL;
240 			goto out;
241 		}
242 		xfrm_state_insert(t);
243 		xfrm_state_hold(t);
244 	}
245 	x->tunnel = t;
246 	atomic_inc(&t->tunnel_users);
247 
248 out:
249 	return err;
250 }
251 
252 static void ipcomp6_free_scratches(void)
253 {
254 	int i;
255 	void **scratches;
256 
257 	if (--ipcomp6_scratch_users)
258 		return;
259 
260 	scratches = ipcomp6_scratches;
261 	if (!scratches)
262 		return;
263 
264 	for_each_possible_cpu(i) {
265 		void *scratch = *per_cpu_ptr(scratches, i);
266 
267 		vfree(scratch);
268 	}
269 
270 	free_percpu(scratches);
271 }
272 
273 static void **ipcomp6_alloc_scratches(void)
274 {
275 	int i;
276 	void **scratches;
277 
278 	if (ipcomp6_scratch_users++)
279 		return ipcomp6_scratches;
280 
281 	scratches = alloc_percpu(void *);
282 	if (!scratches)
283 		return NULL;
284 
285 	ipcomp6_scratches = scratches;
286 
287 	for_each_possible_cpu(i) {
288 		void *scratch = vmalloc(IPCOMP_SCRATCH_SIZE);
289 		if (!scratch)
290 			return NULL;
291 		*per_cpu_ptr(scratches, i) = scratch;
292 	}
293 
294 	return scratches;
295 }
296 
297 static void ipcomp6_free_tfms(struct crypto_comp **tfms)
298 {
299 	struct ipcomp6_tfms *pos;
300 	int cpu;
301 
302 	list_for_each_entry(pos, &ipcomp6_tfms_list, list) {
303 		if (pos->tfms == tfms)
304 			break;
305 	}
306 
307 	BUG_TRAP(pos);
308 
309 	if (--pos->users)
310 		return;
311 
312 	list_del(&pos->list);
313 	kfree(pos);
314 
315 	if (!tfms)
316 		return;
317 
318 	for_each_possible_cpu(cpu) {
319 		struct crypto_comp *tfm = *per_cpu_ptr(tfms, cpu);
320 		crypto_free_comp(tfm);
321 	}
322 	free_percpu(tfms);
323 }
324 
325 static struct crypto_comp **ipcomp6_alloc_tfms(const char *alg_name)
326 {
327 	struct ipcomp6_tfms *pos;
328 	struct crypto_comp **tfms;
329 	int cpu;
330 
331 	/* This can be any valid CPU ID so we don't need locking. */
332 	cpu = raw_smp_processor_id();
333 
334 	list_for_each_entry(pos, &ipcomp6_tfms_list, list) {
335 		struct crypto_comp *tfm;
336 
337 		tfms = pos->tfms;
338 		tfm = *per_cpu_ptr(tfms, cpu);
339 
340 		if (!strcmp(crypto_comp_name(tfm), alg_name)) {
341 			pos->users++;
342 			return tfms;
343 		}
344 	}
345 
346 	pos = kmalloc(sizeof(*pos), GFP_KERNEL);
347 	if (!pos)
348 		return NULL;
349 
350 	pos->users = 1;
351 	INIT_LIST_HEAD(&pos->list);
352 	list_add(&pos->list, &ipcomp6_tfms_list);
353 
354 	pos->tfms = tfms = alloc_percpu(struct crypto_comp *);
355 	if (!tfms)
356 		goto error;
357 
358 	for_each_possible_cpu(cpu) {
359 		struct crypto_comp *tfm = crypto_alloc_comp(alg_name, 0,
360 							    CRYPTO_ALG_ASYNC);
361 		if (!tfm)
362 			goto error;
363 		*per_cpu_ptr(tfms, cpu) = tfm;
364 	}
365 
366 	return tfms;
367 
368 error:
369 	ipcomp6_free_tfms(tfms);
370 	return NULL;
371 }
372 
373 static void ipcomp6_free_data(struct ipcomp_data *ipcd)
374 {
375 	if (ipcd->tfms)
376 		ipcomp6_free_tfms(ipcd->tfms);
377 	ipcomp6_free_scratches();
378 }
379 
380 static void ipcomp6_destroy(struct xfrm_state *x)
381 {
382 	struct ipcomp_data *ipcd = x->data;
383 	if (!ipcd)
384 		return;
385 	xfrm_state_delete_tunnel(x);
386 	mutex_lock(&ipcomp6_resource_mutex);
387 	ipcomp6_free_data(ipcd);
388 	mutex_unlock(&ipcomp6_resource_mutex);
389 	kfree(ipcd);
390 
391 	xfrm6_tunnel_free_spi((xfrm_address_t *)&x->props.saddr);
392 }
393 
394 static int ipcomp6_init_state(struct xfrm_state *x)
395 {
396 	int err;
397 	struct ipcomp_data *ipcd;
398 	struct xfrm_algo_desc *calg_desc;
399 
400 	err = -EINVAL;
401 	if (!x->calg)
402 		goto out;
403 
404 	if (x->encap)
405 		goto out;
406 
407 	err = -ENOMEM;
408 	ipcd = kzalloc(sizeof(*ipcd), GFP_KERNEL);
409 	if (!ipcd)
410 		goto out;
411 
412 	x->props.header_len = 0;
413 	switch (x->props.mode) {
414 	case XFRM_MODE_BEET:
415 	case XFRM_MODE_TRANSPORT:
416 		break;
417 	case XFRM_MODE_TUNNEL:
418 		x->props.header_len += sizeof(struct ipv6hdr);
419 	default:
420 		goto error;
421 	}
422 
423 	mutex_lock(&ipcomp6_resource_mutex);
424 	if (!ipcomp6_alloc_scratches())
425 		goto error;
426 
427 	ipcd->tfms = ipcomp6_alloc_tfms(x->calg->alg_name);
428 	if (!ipcd->tfms)
429 		goto error;
430 	mutex_unlock(&ipcomp6_resource_mutex);
431 
432 	if (x->props.mode == XFRM_MODE_TUNNEL) {
433 		err = ipcomp6_tunnel_attach(x);
434 		if (err)
435 			goto error_tunnel;
436 	}
437 
438 	calg_desc = xfrm_calg_get_byname(x->calg->alg_name, 0);
439 	BUG_ON(!calg_desc);
440 	ipcd->threshold = calg_desc->uinfo.comp.threshold;
441 	x->data = ipcd;
442 	err = 0;
443 out:
444 	return err;
445 error_tunnel:
446 	mutex_lock(&ipcomp6_resource_mutex);
447 error:
448 	ipcomp6_free_data(ipcd);
449 	mutex_unlock(&ipcomp6_resource_mutex);
450 	kfree(ipcd);
451 
452 	goto out;
453 }
454 
455 static struct xfrm_type ipcomp6_type =
456 {
457 	.description	= "IPCOMP6",
458 	.owner		= THIS_MODULE,
459 	.proto		= IPPROTO_COMP,
460 	.init_state	= ipcomp6_init_state,
461 	.destructor	= ipcomp6_destroy,
462 	.input		= ipcomp6_input,
463 	.output		= ipcomp6_output,
464 	.hdr_offset	= xfrm6_find_1stfragopt,
465 };
466 
467 static struct inet6_protocol ipcomp6_protocol =
468 {
469 	.handler	= xfrm6_rcv,
470 	.err_handler	= ipcomp6_err,
471 	.flags		= INET6_PROTO_NOPOLICY,
472 };
473 
474 static int __init ipcomp6_init(void)
475 {
476 	if (xfrm_register_type(&ipcomp6_type, AF_INET6) < 0) {
477 		printk(KERN_INFO "ipcomp6 init: can't add xfrm type\n");
478 		return -EAGAIN;
479 	}
480 	if (inet6_add_protocol(&ipcomp6_protocol, IPPROTO_COMP) < 0) {
481 		printk(KERN_INFO "ipcomp6 init: can't add protocol\n");
482 		xfrm_unregister_type(&ipcomp6_type, AF_INET6);
483 		return -EAGAIN;
484 	}
485 	return 0;
486 }
487 
488 static void __exit ipcomp6_fini(void)
489 {
490 	if (inet6_del_protocol(&ipcomp6_protocol, IPPROTO_COMP) < 0)
491 		printk(KERN_INFO "ipv6 ipcomp close: can't remove protocol\n");
492 	if (xfrm_unregister_type(&ipcomp6_type, AF_INET6) < 0)
493 		printk(KERN_INFO "ipv6 ipcomp close: can't remove xfrm type\n");
494 }
495 
496 module_init(ipcomp6_init);
497 module_exit(ipcomp6_fini);
498 MODULE_LICENSE("GPL");
499 MODULE_DESCRIPTION("IP Payload Compression Protocol (IPComp) for IPv6 - RFC3173");
500 MODULE_AUTHOR("Mitsuru KANDA <mk@linux-ipv6.org>");
501 
502 MODULE_ALIAS_XFRM_TYPE(AF_INET6, XFRM_PROTO_COMP);
503