5604b253bc3e3d47c62caf94e96b7fd5353446f9
[linux-2.6.git] / net / ipv4 / ipcomp.c
1 /*
2  * IP Payload Compression Protocol (IPComp) - RFC3173.
3  *
4  * Copyright (c) 2003 James Morris <jmorris@intercode.com.au>
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the Free
8  * Software Foundation; either version 2 of the License, or (at your option) 
9  * any later version.
10  *
11  * Todo:
12  *   - Tunable compression parameters.
13  *   - Compression stats.
14  *   - Adaptive compression.
15  */
16 #include <linux/config.h>
17 #include <linux/module.h>
18 #include <asm/scatterlist.h>
19 #include <linux/crypto.h>
20 #include <linux/pfkeyv2.h>
21 #include <net/inet_ecn.h>
22 #include <net/ip.h>
23 #include <net/xfrm.h>
24 #include <net/icmp.h>
25 #include <net/ipcomp.h>
26
27 static int ipcomp_decompress(struct xfrm_state *x, struct sk_buff *skb)
28 {
29         int err, plen, dlen;
30         struct iphdr *iph;
31         struct ipcomp_data *ipcd = x->data;
32         u8 *start, *scratch = ipcd->scratch;
33         
34         plen = skb->len;
35         dlen = IPCOMP_SCRATCH_SIZE;
36         start = skb->data;
37
38         err = crypto_comp_decompress(ipcd->tfm, start, plen, scratch, &dlen);
39         if (err)
40                 goto out;
41
42         if (dlen < (plen + sizeof(struct ip_comp_hdr))) {
43                 err = -EINVAL;
44                 goto out;
45         }
46
47         err = pskb_expand_head(skb, 0, dlen - plen, GFP_ATOMIC);
48         if (err)
49                 goto out;
50                 
51         skb_put(skb, dlen - plen);
52         memcpy(skb->data, scratch, dlen);
53         iph = skb->nh.iph;
54         iph->tot_len = htons(dlen + iph->ihl * 4);
55 out:    
56         return err;
57 }
58
59 static int ipcomp_input(struct xfrm_state *x,
60                         struct xfrm_decap_state *decap, struct sk_buff *skb)
61 {
62         u8 nexthdr;
63         int err = 0;
64         struct iphdr *iph;
65         union {
66                 struct iphdr    iph;
67                 char            buf[60];
68         } tmp_iph;
69
70
71         if ((skb_is_nonlinear(skb) || skb_cloned(skb)) &&
72             skb_linearize(skb, GFP_ATOMIC) != 0) {
73                 err = -ENOMEM;
74                 goto out;
75         }
76
77         skb->ip_summed = CHECKSUM_NONE;
78
79         /* Remove ipcomp header and decompress original payload */      
80         iph = skb->nh.iph;
81         memcpy(&tmp_iph, iph, iph->ihl * 4);
82         nexthdr = *(u8 *)skb->data;
83         skb_pull(skb, sizeof(struct ip_comp_hdr));
84         skb->nh.raw += sizeof(struct ip_comp_hdr);
85         memcpy(skb->nh.raw, &tmp_iph, tmp_iph.iph.ihl * 4);
86         iph = skb->nh.iph;
87         iph->tot_len = htons(ntohs(iph->tot_len) - sizeof(struct ip_comp_hdr));
88         iph->protocol = nexthdr;
89         skb->h.raw = skb->data;
90         err = ipcomp_decompress(x, skb);
91
92 out:    
93         return err;
94 }
95
96 static int ipcomp_compress(struct xfrm_state *x, struct sk_buff *skb)
97 {
98         int err, plen, dlen, ihlen;
99         struct iphdr *iph = skb->nh.iph;
100         struct ipcomp_data *ipcd = x->data;
101         u8 *start, *scratch = ipcd->scratch;
102         
103         ihlen = iph->ihl * 4;
104         plen = skb->len - ihlen;
105         dlen = IPCOMP_SCRATCH_SIZE;
106         start = skb->data + ihlen;
107
108         err = crypto_comp_compress(ipcd->tfm, start, plen, scratch, &dlen);
109         if (err)
110                 goto out;
111
112         if ((dlen + sizeof(struct ip_comp_hdr)) >= plen) {
113                 err = -EMSGSIZE;
114                 goto out;
115         }
116         
117         memcpy(start + sizeof(struct ip_comp_hdr), scratch, dlen);
118         pskb_trim(skb, ihlen + dlen + sizeof(struct ip_comp_hdr));
119         
120 out:    
121         return err;
122 }
123
124 static int ipcomp_output(struct sk_buff **pskb)
125 {
126         int err;
127         struct dst_entry *dst = (*pskb)->dst;
128         struct xfrm_state *x = dst->xfrm;
129         struct iphdr *iph;
130         struct ip_comp_hdr *ipch;
131         struct ipcomp_data *ipcd = x->data;
132         int hdr_len = 0;
133
134         iph = (*pskb)->nh.iph;
135         iph->tot_len = htons((*pskb)->len);
136         hdr_len = iph->ihl * 4;
137         if (((*pskb)->len - hdr_len) < ipcd->threshold) {
138                 /* Don't bother compressing */
139                 if (x->props.mode) {
140                         ip_send_check(iph);
141                 }
142                 goto out_ok;
143         }
144
145         if ((skb_is_nonlinear(*pskb) || skb_cloned(*pskb)) &&
146             skb_linearize(*pskb, GFP_ATOMIC) != 0) {
147                 err = -ENOMEM;
148                 goto error;
149         }
150         
151         err = ipcomp_compress(x, *pskb);
152         if (err) {
153                 if (err == -EMSGSIZE) {
154                         if (x->props.mode) {
155                                 iph = (*pskb)->nh.iph;
156                                 ip_send_check(iph);
157                         }
158                         goto out_ok;
159                 }
160                 goto error;
161         }
162
163         /* Install ipcomp header, convert into ipcomp datagram. */
164         iph = (*pskb)->nh.iph;
165         iph->tot_len = htons((*pskb)->len);
166         ipch = (struct ip_comp_hdr *)((char *)iph + iph->ihl * 4);
167         ipch->nexthdr = iph->protocol;
168         ipch->flags = 0;
169         ipch->cpi = htons((u16 )ntohl(x->id.spi));
170         iph->protocol = IPPROTO_COMP;
171         ip_send_check(iph);
172
173 out_ok:
174         err = 0;
175
176 error:
177         return err;
178 }
179
180 static void ipcomp4_err(struct sk_buff *skb, u32 info)
181 {
182         u32 spi;
183         struct iphdr *iph = (struct iphdr *)skb->data;
184         struct ip_comp_hdr *ipch = (struct ip_comp_hdr *)(skb->data+(iph->ihl<<2));
185         struct xfrm_state *x;
186
187         if (skb->h.icmph->type != ICMP_DEST_UNREACH ||
188             skb->h.icmph->code != ICMP_FRAG_NEEDED)
189                 return;
190
191         spi = ntohl(ntohs(ipch->cpi));
192         x = xfrm_state_lookup((xfrm_address_t *)&iph->daddr,
193                               spi, IPPROTO_COMP, AF_INET);
194         if (!x)
195                 return;
196         NETDEBUG(printk(KERN_DEBUG "pmtu discovery on SA IPCOMP/%08x/%u.%u.%u.%u\n",
197                spi, NIPQUAD(iph->daddr)));
198         xfrm_state_put(x);
199 }
200
201 /* We always hold one tunnel user reference to indicate a tunnel */ 
202 static struct xfrm_state *ipcomp_tunnel_create(struct xfrm_state *x)
203 {
204         struct xfrm_state *t;
205         
206         t = xfrm_state_alloc();
207         if (t == NULL)
208                 goto out;
209
210         t->id.proto = IPPROTO_IPIP;
211         t->id.spi = x->props.saddr.a4;
212         t->id.daddr.a4 = x->id.daddr.a4;
213         memcpy(&t->sel, &x->sel, sizeof(t->sel));
214         t->props.family = AF_INET;
215         t->props.mode = 1;
216         t->props.saddr.a4 = x->props.saddr.a4;
217         t->props.flags = x->props.flags;
218         
219         t->type = xfrm_get_type(IPPROTO_IPIP, t->props.family);
220         if (t->type == NULL)
221                 goto error;
222                 
223         if (t->type->init_state(t, NULL))
224                 goto error;
225
226         t->km.state = XFRM_STATE_VALID;
227         atomic_set(&t->tunnel_users, 1);
228 out:
229         return t;
230
231 error:
232         t->km.state = XFRM_STATE_DEAD;
233         xfrm_state_put(t);
234         t = NULL;
235         goto out;
236 }
237
238 /*
239  * Must be protected by xfrm_cfg_sem.  State and tunnel user references are
240  * always incremented on success.
241  */
242 static int ipcomp_tunnel_attach(struct xfrm_state *x)
243 {
244         int err = 0;
245         struct xfrm_state *t;
246
247         t = xfrm_state_lookup((xfrm_address_t *)&x->id.daddr.a4,
248                               x->props.saddr.a4, IPPROTO_IPIP, AF_INET);
249         if (!t) {
250                 t = ipcomp_tunnel_create(x);
251                 if (!t) {
252                         err = -EINVAL;
253                         goto out;
254                 }
255                 xfrm_state_insert(t);
256                 xfrm_state_hold(t);
257         }
258         x->tunnel = t;
259         atomic_inc(&t->tunnel_users);
260 out:
261         return err;
262 }
263
264 static void ipcomp_free_data(struct ipcomp_data *ipcd)
265 {
266         if (ipcd->tfm)
267                 crypto_free_tfm(ipcd->tfm);
268         if (ipcd->scratch)
269                 kfree(ipcd->scratch);   
270 }
271
272 static void ipcomp_destroy(struct xfrm_state *x)
273 {
274         struct ipcomp_data *ipcd = x->data;
275         if (!ipcd)
276                 return;
277         xfrm_state_delete_tunnel(x);
278         ipcomp_free_data(ipcd);
279         kfree(ipcd);
280 }
281
282 static int ipcomp_init_state(struct xfrm_state *x, void *args)
283 {
284         int err;
285         struct ipcomp_data *ipcd;
286         struct xfrm_algo_desc *calg_desc;
287
288         err = -EINVAL;
289         if (!x->calg)
290                 goto out;
291
292         err = -ENOMEM;
293         ipcd = kmalloc(sizeof(*ipcd), GFP_KERNEL);
294         if (!ipcd)
295                 goto error;
296
297         memset(ipcd, 0, sizeof(*ipcd));
298         x->props.header_len = 0;
299         if (x->props.mode)
300                 x->props.header_len += sizeof(struct iphdr);
301
302         ipcd->scratch = kmalloc(IPCOMP_SCRATCH_SIZE, GFP_KERNEL);
303         if (!ipcd->scratch)
304                 goto error;
305         
306         ipcd->tfm = crypto_alloc_tfm(x->calg->alg_name, 0);
307         if (!ipcd->tfm)
308                 goto error;
309
310         if (x->props.mode) {
311                 err = ipcomp_tunnel_attach(x);
312                 if (err)
313                         goto error;
314         }
315
316         calg_desc = xfrm_calg_get_byname(x->calg->alg_name);
317         BUG_ON(!calg_desc);
318         ipcd->threshold = calg_desc->uinfo.comp.threshold;
319         x->data = ipcd;
320         err = 0;
321 out:
322         return err;
323
324 error:
325         if (ipcd) {
326                 ipcomp_free_data(ipcd);
327                 kfree(ipcd);
328         }
329         goto out;
330 }
331
332 static struct xfrm_type ipcomp_type = {
333         .description    = "IPCOMP4",
334         .owner          = THIS_MODULE,
335         .proto          = IPPROTO_COMP,
336         .init_state     = ipcomp_init_state,
337         .destructor     = ipcomp_destroy,
338         .input          = ipcomp_input,
339         .output         = ipcomp_output
340 };
341
342 static struct net_protocol ipcomp4_protocol = {
343         .handler        =       xfrm4_rcv,
344         .err_handler    =       ipcomp4_err,
345         .no_policy      =       1,
346 };
347
348 static int __init ipcomp4_init(void)
349 {
350         if (xfrm_register_type(&ipcomp_type, AF_INET) < 0) {
351                 printk(KERN_INFO "ipcomp init: can't add xfrm type\n");
352                 return -EAGAIN;
353         }
354         if (inet_add_protocol(&ipcomp4_protocol, IPPROTO_COMP) < 0) {
355                 printk(KERN_INFO "ipcomp init: can't add protocol\n");
356                 xfrm_unregister_type(&ipcomp_type, AF_INET);
357                 return -EAGAIN;
358         }
359         return 0;
360 }
361
362 static void __exit ipcomp4_fini(void)
363 {
364         if (inet_del_protocol(&ipcomp4_protocol, IPPROTO_COMP) < 0)
365                 printk(KERN_INFO "ip ipcomp close: can't remove protocol\n");
366         if (xfrm_unregister_type(&ipcomp_type, AF_INET) < 0)
367                 printk(KERN_INFO "ip ipcomp close: can't remove xfrm type\n");
368 }
369
370 module_init(ipcomp4_init);
371 module_exit(ipcomp4_fini);
372
373 MODULE_LICENSE("GPL");
374 MODULE_DESCRIPTION("IP Payload Compression Protocol (IPComp) - RFC3173");
375 MODULE_AUTHOR("James Morris <jmorris@intercode.com.au>");
376