VServer 1.9.2 (patch-2.6.8.1-vs1.9.2.diff)
[linux-2.6.git] / net / ipv6 / ipcomp6.c
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/config.h>
34 #include <linux/module.h>
35 #include <net/ip.h>
36 #include <net/xfrm.h>
37 #include <net/ipcomp.h>
38 #include <asm/scatterlist.h>
39 #include <linux/crypto.h>
40 #include <linux/pfkeyv2.h>
41 #include <linux/random.h>
42 #include <net/icmp.h>
43 #include <net/ipv6.h>
44 #include <linux/ipv6.h>
45 #include <linux/icmpv6.h>
46
47 static int ipcomp6_input(struct xfrm_state *x, struct xfrm_decap_state *decap, struct sk_buff *skb)
48 {
49         int err = 0;
50         u8 nexthdr = 0;
51         int hdr_len = skb->h.raw - skb->nh.raw;
52         unsigned char *tmp_hdr = NULL;
53         struct ipv6hdr *iph;
54         int plen, dlen;
55         struct ipcomp_data *ipcd = x->data;
56         u8 *start, *scratch = ipcd->scratch;
57
58         if ((skb_is_nonlinear(skb) || skb_cloned(skb)) &&
59                 skb_linearize(skb, GFP_ATOMIC) != 0) {
60                 err = -ENOMEM;
61                 goto out;
62         }
63
64         skb->ip_summed = CHECKSUM_NONE;
65
66         /* Remove ipcomp header and decompress original payload */
67         iph = skb->nh.ipv6h;
68         tmp_hdr = kmalloc(hdr_len, GFP_ATOMIC);
69         if (!tmp_hdr)
70                 goto out;
71         memcpy(tmp_hdr, iph, hdr_len);
72         nexthdr = *(u8 *)skb->data;
73         skb_pull(skb, sizeof(struct ipv6_comp_hdr)); 
74         skb->nh.raw += sizeof(struct ipv6_comp_hdr);
75         memcpy(skb->nh.raw, tmp_hdr, hdr_len);
76         iph = skb->nh.ipv6h;
77         iph->payload_len = htons(ntohs(iph->payload_len) - sizeof(struct ipv6_comp_hdr));
78         skb->h.raw = skb->data;
79
80         /* decompression */
81         plen = skb->len;
82         dlen = IPCOMP_SCRATCH_SIZE;
83         start = skb->data;
84
85         err = crypto_comp_decompress(ipcd->tfm, start, plen, scratch, &dlen);
86         if (err) {
87                 err = -EINVAL;
88                 goto out;
89         }
90
91         if (dlen < (plen + sizeof(struct ipv6_comp_hdr))) {
92                 err = -EINVAL;
93                 goto out;
94         }
95
96         err = pskb_expand_head(skb, 0, dlen - plen, GFP_ATOMIC);
97         if (err) {
98                 goto out;
99         }
100
101         skb_put(skb, dlen - plen);
102         memcpy(skb->data, scratch, dlen);
103
104         iph = skb->nh.ipv6h;
105         iph->payload_len = htons(skb->len);
106         
107 out:
108         if (tmp_hdr)
109                 kfree(tmp_hdr);
110         if (err)
111                 goto error_out;
112         return nexthdr;
113 error_out:
114         return err;
115 }
116
117 static int ipcomp6_output(struct sk_buff **pskb)
118 {
119         int err;
120         struct dst_entry *dst = (*pskb)->dst;
121         struct xfrm_state *x = dst->xfrm;
122         struct ipv6hdr *top_iph;
123         int hdr_len;
124         struct ipv6_comp_hdr *ipch;
125         struct ipcomp_data *ipcd = x->data;
126         int plen, dlen;
127         u8 *start, *scratch = ipcd->scratch;
128
129         hdr_len = (*pskb)->h.raw - (*pskb)->data;
130
131         /* check whether datagram len is larger than threshold */
132         if (((*pskb)->len - hdr_len) < ipcd->threshold) {
133                 goto out_ok;
134         }
135
136         if ((skb_is_nonlinear(*pskb) || skb_cloned(*pskb)) &&
137                 skb_linearize(*pskb, GFP_ATOMIC) != 0) {
138                 err = -ENOMEM;
139                 goto error;
140         }
141
142         /* compression */
143         plen = (*pskb)->len - hdr_len;
144         dlen = IPCOMP_SCRATCH_SIZE;
145         start = (*pskb)->h.raw;
146
147         err = crypto_comp_compress(ipcd->tfm, start, plen, scratch, &dlen);
148         if (err) {
149                 goto error;
150         }
151         if ((dlen + sizeof(struct ipv6_comp_hdr)) >= plen) {
152                 goto out_ok;
153         }
154         memcpy(start + sizeof(struct ip_comp_hdr), scratch, dlen);
155         pskb_trim(*pskb, hdr_len + dlen + sizeof(struct ip_comp_hdr));
156
157         /* insert ipcomp header and replace datagram */
158         top_iph = (struct ipv6hdr *)(*pskb)->data;
159
160         top_iph->payload_len = htons((*pskb)->len - sizeof(struct ipv6hdr));
161
162         ipch = (struct ipv6_comp_hdr *)start;
163         ipch->nexthdr = *(*pskb)->nh.raw;
164         ipch->flags = 0;
165         ipch->cpi = htons((u16 )ntohl(x->id.spi));
166         *(*pskb)->nh.raw = IPPROTO_COMP;
167
168 out_ok:
169         err = 0;
170
171 error:
172         return err;
173 }
174
175 static void ipcomp6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
176                                 int type, int code, int offset, __u32 info)
177 {
178         u32 spi;
179         struct ipv6hdr *iph = (struct ipv6hdr*)skb->data;
180         struct ipv6_comp_hdr *ipcomph = (struct ipv6_comp_hdr*)(skb->data+offset);
181         struct xfrm_state *x;
182
183         if (type != ICMPV6_DEST_UNREACH && type != ICMPV6_PKT_TOOBIG)
184                 return;
185
186         spi = ntohl(ntohs(ipcomph->cpi));
187         x = xfrm_state_lookup((xfrm_address_t *)&iph->daddr, spi, IPPROTO_COMP, AF_INET6);
188         if (!x)
189                 return;
190
191         printk(KERN_DEBUG "pmtu discovery on SA IPCOMP/%08x/"
192                         "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
193                         spi, NIP6(iph->daddr));
194         xfrm_state_put(x);
195 }
196
197 static struct xfrm_state *ipcomp6_tunnel_create(struct xfrm_state *x)
198 {
199         struct xfrm_state *t = NULL;
200
201         t = xfrm_state_alloc();
202         if (!t)
203                 goto out;
204
205         t->id.proto = IPPROTO_IPV6;
206         t->id.spi = xfrm6_tunnel_alloc_spi((xfrm_address_t *)&x->props.saddr);
207         memcpy(t->id.daddr.a6, x->id.daddr.a6, sizeof(struct in6_addr));
208         memcpy(&t->sel, &x->sel, sizeof(t->sel));
209         t->props.family = AF_INET6;
210         t->props.mode = 1;
211         memcpy(t->props.saddr.a6, x->props.saddr.a6, sizeof(struct in6_addr));
212
213         t->type = xfrm_get_type(IPPROTO_IPV6, t->props.family);
214         if (t->type == NULL)
215                 goto error;
216
217         if (t->type->init_state(t, NULL))
218                 goto error;
219
220         t->km.state = XFRM_STATE_VALID;
221         atomic_set(&t->tunnel_users, 1);
222
223 out:
224         return t;
225
226 error:
227         xfrm_state_put(t);
228         goto out;
229 }
230
231 static int ipcomp6_tunnel_attach(struct xfrm_state *x)
232 {
233         int err = 0;
234         struct xfrm_state *t = NULL;
235         u32 spi;
236
237         spi = xfrm6_tunnel_spi_lookup((xfrm_address_t *)&x->props.saddr);
238         if (spi)
239                 t = xfrm_state_lookup((xfrm_address_t *)&x->id.daddr,
240                                               spi, IPPROTO_IPV6, AF_INET6);
241         if (!t) {
242                 t = ipcomp6_tunnel_create(x);
243                 if (!t) {
244                         err = -EINVAL;
245                         goto out;
246                 }
247                 xfrm_state_insert(t);
248                 xfrm_state_hold(t);
249         }
250         x->tunnel = t;
251         atomic_inc(&t->tunnel_users);
252
253 out:
254         return err;
255 }
256
257 static void ipcomp6_free_data(struct ipcomp_data *ipcd)
258 {
259         if (ipcd->tfm)
260                 crypto_free_tfm(ipcd->tfm);
261         if (ipcd->scratch)
262                 kfree(ipcd->scratch);
263 }
264
265 static void ipcomp6_destroy(struct xfrm_state *x)
266 {
267         struct ipcomp_data *ipcd = x->data;
268         if (!ipcd)
269                 return;
270         xfrm_state_delete_tunnel(x);
271         ipcomp6_free_data(ipcd);
272         kfree(ipcd);
273
274         xfrm6_tunnel_free_spi((xfrm_address_t *)&x->props.saddr);
275 }
276
277 static int ipcomp6_init_state(struct xfrm_state *x, void *args)
278 {
279         int err;
280         struct ipcomp_data *ipcd;
281         struct xfrm_algo_desc *calg_desc;
282
283         err = -EINVAL;
284         if (!x->calg)
285                 goto out;
286
287         err = -ENOMEM;
288         ipcd = kmalloc(sizeof(*ipcd), GFP_KERNEL);
289         if (!ipcd)
290                 goto error;
291
292         memset(ipcd, 0, sizeof(*ipcd));
293         x->props.header_len = 0;
294         if (x->props.mode)
295                 x->props.header_len += sizeof(struct ipv6hdr);
296         
297         ipcd->scratch = kmalloc(IPCOMP_SCRATCH_SIZE, GFP_KERNEL);
298         if (!ipcd->scratch)
299                 goto error;
300
301         ipcd->tfm = crypto_alloc_tfm(x->calg->alg_name, 0);
302         if (!ipcd->tfm)
303                 goto error;
304
305         if (x->props.mode) {
306                 err = ipcomp6_tunnel_attach(x);
307                 if (err)
308                         goto error;
309         }
310
311         calg_desc = xfrm_calg_get_byname(x->calg->alg_name);
312         BUG_ON(!calg_desc);
313         ipcd->threshold = calg_desc->uinfo.comp.threshold;
314         x->data = ipcd;
315         err = 0;
316 out:
317         return err;
318 error:
319         if (ipcd) {
320                 ipcomp6_free_data(ipcd);
321                 kfree(ipcd);
322         }
323
324         goto out;
325 }
326
327 static struct xfrm_type ipcomp6_type = 
328 {
329         .description    = "IPCOMP6",
330         .owner          = THIS_MODULE,
331         .proto          = IPPROTO_COMP,
332         .init_state     = ipcomp6_init_state,
333         .destructor     = ipcomp6_destroy,
334         .input          = ipcomp6_input,
335         .output         = ipcomp6_output,
336 };
337
338 static struct inet6_protocol ipcomp6_protocol = 
339 {
340         .handler        = xfrm6_rcv,
341         .err_handler    = ipcomp6_err,
342         .flags          = INET6_PROTO_NOPOLICY,
343 };
344
345 static int __init ipcomp6_init(void)
346 {
347         if (xfrm_register_type(&ipcomp6_type, AF_INET6) < 0) {
348                 printk(KERN_INFO "ipcomp6 init: can't add xfrm type\n");
349                 return -EAGAIN;
350         }
351         if (inet6_add_protocol(&ipcomp6_protocol, IPPROTO_COMP) < 0) {
352                 printk(KERN_INFO "ipcomp6 init: can't add protocol\n");
353                 xfrm_unregister_type(&ipcomp6_type, AF_INET6);
354                 return -EAGAIN;
355         }
356         return 0;
357 }
358
359 static void __exit ipcomp6_fini(void)
360 {
361         if (inet6_del_protocol(&ipcomp6_protocol, IPPROTO_COMP) < 0) 
362                 printk(KERN_INFO "ipv6 ipcomp close: can't remove protocol\n");
363         if (xfrm_unregister_type(&ipcomp6_type, AF_INET6) < 0)
364                 printk(KERN_INFO "ipv6 ipcomp close: can't remove xfrm type\n");
365 }
366
367 module_init(ipcomp6_init);
368 module_exit(ipcomp6_fini);
369 MODULE_LICENSE("GPL");
370 MODULE_DESCRIPTION("IP Payload Compression Protocol (IPComp) for IPv6 - RFC3173");
371 MODULE_AUTHOR("Mitsuru KANDA <mk@linux-ipv6.org>");
372
373