diff options
Diffstat (limited to 'net/ipv6/ah6.c')
-rw-r--r-- | net/ipv6/ah6.c | 478 |
1 files changed, 478 insertions, 0 deletions
diff --git a/net/ipv6/ah6.c b/net/ipv6/ah6.c new file mode 100644 index 000000000000..e3ecf626cbf7 --- /dev/null +++ b/net/ipv6/ah6.c | |||
@@ -0,0 +1,478 @@ | |||
1 | /* | ||
2 | * Copyright (C)2002 USAGI/WIDE Project | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the Free Software | ||
16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
17 | * | ||
18 | * Authors | ||
19 | * | ||
20 | * Mitsuru KANDA @USAGI : IPv6 Support | ||
21 | * Kazunori MIYAZAWA @USAGI : | ||
22 | * Kunihiro Ishiguro <kunihiro@ipinfusion.com> | ||
23 | * | ||
24 | * This file is derived from net/ipv4/ah.c. | ||
25 | */ | ||
26 | |||
27 | #include <linux/config.h> | ||
28 | #include <linux/module.h> | ||
29 | #include <net/ip.h> | ||
30 | #include <net/ah.h> | ||
31 | #include <linux/crypto.h> | ||
32 | #include <linux/pfkeyv2.h> | ||
33 | #include <linux/string.h> | ||
34 | #include <net/icmp.h> | ||
35 | #include <net/ipv6.h> | ||
36 | #include <net/xfrm.h> | ||
37 | #include <asm/scatterlist.h> | ||
38 | |||
39 | static int zero_out_mutable_opts(struct ipv6_opt_hdr *opthdr) | ||
40 | { | ||
41 | u8 *opt = (u8 *)opthdr; | ||
42 | int len = ipv6_optlen(opthdr); | ||
43 | int off = 0; | ||
44 | int optlen = 0; | ||
45 | |||
46 | off += 2; | ||
47 | len -= 2; | ||
48 | |||
49 | while (len > 0) { | ||
50 | |||
51 | switch (opt[off]) { | ||
52 | |||
53 | case IPV6_TLV_PAD0: | ||
54 | optlen = 1; | ||
55 | break; | ||
56 | default: | ||
57 | if (len < 2) | ||
58 | goto bad; | ||
59 | optlen = opt[off+1]+2; | ||
60 | if (len < optlen) | ||
61 | goto bad; | ||
62 | if (opt[off] & 0x20) | ||
63 | memset(&opt[off+2], 0, opt[off+1]); | ||
64 | break; | ||
65 | } | ||
66 | |||
67 | off += optlen; | ||
68 | len -= optlen; | ||
69 | } | ||
70 | if (len == 0) | ||
71 | return 1; | ||
72 | |||
73 | bad: | ||
74 | return 0; | ||
75 | } | ||
76 | |||
77 | /** | ||
78 | * ipv6_rearrange_rthdr - rearrange IPv6 routing header | ||
79 | * @iph: IPv6 header | ||
80 | * @rthdr: routing header | ||
81 | * | ||
82 | * Rearrange the destination address in @iph and the addresses in @rthdr | ||
83 | * so that they appear in the order they will at the final destination. | ||
84 | * See Appendix A2 of RFC 2402 for details. | ||
85 | */ | ||
86 | static void ipv6_rearrange_rthdr(struct ipv6hdr *iph, struct ipv6_rt_hdr *rthdr) | ||
87 | { | ||
88 | int segments, segments_left; | ||
89 | struct in6_addr *addrs; | ||
90 | struct in6_addr final_addr; | ||
91 | |||
92 | segments_left = rthdr->segments_left; | ||
93 | if (segments_left == 0) | ||
94 | return; | ||
95 | rthdr->segments_left = 0; | ||
96 | |||
97 | /* The value of rthdr->hdrlen has been verified either by the system | ||
98 | * call if it is locally generated, or by ipv6_rthdr_rcv() for incoming | ||
99 | * packets. So we can assume that it is even and that segments is | ||
100 | * greater than or equal to segments_left. | ||
101 | * | ||
102 | * For the same reason we can assume that this option is of type 0. | ||
103 | */ | ||
104 | segments = rthdr->hdrlen >> 1; | ||
105 | |||
106 | addrs = ((struct rt0_hdr *)rthdr)->addr; | ||
107 | ipv6_addr_copy(&final_addr, addrs + segments - 1); | ||
108 | |||
109 | addrs += segments - segments_left; | ||
110 | memmove(addrs + 1, addrs, (segments_left - 1) * sizeof(*addrs)); | ||
111 | |||
112 | ipv6_addr_copy(addrs, &iph->daddr); | ||
113 | ipv6_addr_copy(&iph->daddr, &final_addr); | ||
114 | } | ||
115 | |||
116 | static int ipv6_clear_mutable_options(struct ipv6hdr *iph, int len) | ||
117 | { | ||
118 | union { | ||
119 | struct ipv6hdr *iph; | ||
120 | struct ipv6_opt_hdr *opth; | ||
121 | struct ipv6_rt_hdr *rth; | ||
122 | char *raw; | ||
123 | } exthdr = { .iph = iph }; | ||
124 | char *end = exthdr.raw + len; | ||
125 | int nexthdr = iph->nexthdr; | ||
126 | |||
127 | exthdr.iph++; | ||
128 | |||
129 | while (exthdr.raw < end) { | ||
130 | switch (nexthdr) { | ||
131 | case NEXTHDR_HOP: | ||
132 | case NEXTHDR_DEST: | ||
133 | if (!zero_out_mutable_opts(exthdr.opth)) { | ||
134 | LIMIT_NETDEBUG(printk( | ||
135 | KERN_WARNING "overrun %sopts\n", | ||
136 | nexthdr == NEXTHDR_HOP ? | ||
137 | "hop" : "dest")); | ||
138 | return -EINVAL; | ||
139 | } | ||
140 | break; | ||
141 | |||
142 | case NEXTHDR_ROUTING: | ||
143 | ipv6_rearrange_rthdr(iph, exthdr.rth); | ||
144 | break; | ||
145 | |||
146 | default : | ||
147 | return 0; | ||
148 | } | ||
149 | |||
150 | nexthdr = exthdr.opth->nexthdr; | ||
151 | exthdr.raw += ipv6_optlen(exthdr.opth); | ||
152 | } | ||
153 | |||
154 | return 0; | ||
155 | } | ||
156 | |||
157 | static int ah6_output(struct xfrm_state *x, struct sk_buff *skb) | ||
158 | { | ||
159 | int err; | ||
160 | int extlen; | ||
161 | struct ipv6hdr *top_iph; | ||
162 | struct ip_auth_hdr *ah; | ||
163 | struct ah_data *ahp; | ||
164 | u8 nexthdr; | ||
165 | char tmp_base[8]; | ||
166 | struct { | ||
167 | struct in6_addr daddr; | ||
168 | char hdrs[0]; | ||
169 | } *tmp_ext; | ||
170 | |||
171 | top_iph = (struct ipv6hdr *)skb->data; | ||
172 | top_iph->payload_len = htons(skb->len - sizeof(*top_iph)); | ||
173 | |||
174 | nexthdr = *skb->nh.raw; | ||
175 | *skb->nh.raw = IPPROTO_AH; | ||
176 | |||
177 | /* When there are no extension headers, we only need to save the first | ||
178 | * 8 bytes of the base IP header. | ||
179 | */ | ||
180 | memcpy(tmp_base, top_iph, sizeof(tmp_base)); | ||
181 | |||
182 | tmp_ext = NULL; | ||
183 | extlen = skb->h.raw - (unsigned char *)(top_iph + 1); | ||
184 | if (extlen) { | ||
185 | extlen += sizeof(*tmp_ext); | ||
186 | tmp_ext = kmalloc(extlen, GFP_ATOMIC); | ||
187 | if (!tmp_ext) { | ||
188 | err = -ENOMEM; | ||
189 | goto error; | ||
190 | } | ||
191 | memcpy(tmp_ext, &top_iph->daddr, extlen); | ||
192 | err = ipv6_clear_mutable_options(top_iph, | ||
193 | extlen - sizeof(*tmp_ext) + | ||
194 | sizeof(*top_iph)); | ||
195 | if (err) | ||
196 | goto error_free_iph; | ||
197 | } | ||
198 | |||
199 | ah = (struct ip_auth_hdr *)skb->h.raw; | ||
200 | ah->nexthdr = nexthdr; | ||
201 | |||
202 | top_iph->priority = 0; | ||
203 | top_iph->flow_lbl[0] = 0; | ||
204 | top_iph->flow_lbl[1] = 0; | ||
205 | top_iph->flow_lbl[2] = 0; | ||
206 | top_iph->hop_limit = 0; | ||
207 | |||
208 | ahp = x->data; | ||
209 | ah->hdrlen = (XFRM_ALIGN8(sizeof(struct ipv6_auth_hdr) + | ||
210 | ahp->icv_trunc_len) >> 2) - 2; | ||
211 | |||
212 | ah->reserved = 0; | ||
213 | ah->spi = x->id.spi; | ||
214 | ah->seq_no = htonl(++x->replay.oseq); | ||
215 | ahp->icv(ahp, skb, ah->auth_data); | ||
216 | |||
217 | err = 0; | ||
218 | |||
219 | memcpy(top_iph, tmp_base, sizeof(tmp_base)); | ||
220 | if (tmp_ext) { | ||
221 | memcpy(&top_iph->daddr, tmp_ext, extlen); | ||
222 | error_free_iph: | ||
223 | kfree(tmp_ext); | ||
224 | } | ||
225 | |||
226 | error: | ||
227 | return err; | ||
228 | } | ||
229 | |||
230 | static int ah6_input(struct xfrm_state *x, struct xfrm_decap_state *decap, struct sk_buff *skb) | ||
231 | { | ||
232 | /* | ||
233 | * Before process AH | ||
234 | * [IPv6][Ext1][Ext2][AH][Dest][Payload] | ||
235 | * |<-------------->| hdr_len | ||
236 | * | ||
237 | * To erase AH: | ||
238 | * Keeping copy of cleared headers. After AH processing, | ||
239 | * Moving the pointer of skb->nh.raw by using skb_pull as long as AH | ||
240 | * header length. Then copy back the copy as long as hdr_len | ||
241 | * If destination header following AH exists, copy it into after [Ext2]. | ||
242 | * | ||
243 | * |<>|[IPv6][Ext1][Ext2][Dest][Payload] | ||
244 | * There is offset of AH before IPv6 header after the process. | ||
245 | */ | ||
246 | |||
247 | struct ipv6_auth_hdr *ah; | ||
248 | struct ah_data *ahp; | ||
249 | unsigned char *tmp_hdr = NULL; | ||
250 | u16 hdr_len; | ||
251 | u16 ah_hlen; | ||
252 | int nexthdr; | ||
253 | |||
254 | if (!pskb_may_pull(skb, sizeof(struct ip_auth_hdr))) | ||
255 | goto out; | ||
256 | |||
257 | /* We are going to _remove_ AH header to keep sockets happy, | ||
258 | * so... Later this can change. */ | ||
259 | if (skb_cloned(skb) && | ||
260 | pskb_expand_head(skb, 0, 0, GFP_ATOMIC)) | ||
261 | goto out; | ||
262 | |||
263 | hdr_len = skb->data - skb->nh.raw; | ||
264 | ah = (struct ipv6_auth_hdr*)skb->data; | ||
265 | ahp = x->data; | ||
266 | nexthdr = ah->nexthdr; | ||
267 | ah_hlen = (ah->hdrlen + 2) << 2; | ||
268 | |||
269 | if (ah_hlen != XFRM_ALIGN8(sizeof(struct ipv6_auth_hdr) + ahp->icv_full_len) && | ||
270 | ah_hlen != XFRM_ALIGN8(sizeof(struct ipv6_auth_hdr) + ahp->icv_trunc_len)) | ||
271 | goto out; | ||
272 | |||
273 | if (!pskb_may_pull(skb, ah_hlen)) | ||
274 | goto out; | ||
275 | |||
276 | tmp_hdr = kmalloc(hdr_len, GFP_ATOMIC); | ||
277 | if (!tmp_hdr) | ||
278 | goto out; | ||
279 | memcpy(tmp_hdr, skb->nh.raw, hdr_len); | ||
280 | if (ipv6_clear_mutable_options(skb->nh.ipv6h, hdr_len)) | ||
281 | goto out; | ||
282 | skb->nh.ipv6h->priority = 0; | ||
283 | skb->nh.ipv6h->flow_lbl[0] = 0; | ||
284 | skb->nh.ipv6h->flow_lbl[1] = 0; | ||
285 | skb->nh.ipv6h->flow_lbl[2] = 0; | ||
286 | skb->nh.ipv6h->hop_limit = 0; | ||
287 | |||
288 | { | ||
289 | u8 auth_data[MAX_AH_AUTH_LEN]; | ||
290 | |||
291 | memcpy(auth_data, ah->auth_data, ahp->icv_trunc_len); | ||
292 | memset(ah->auth_data, 0, ahp->icv_trunc_len); | ||
293 | skb_push(skb, skb->data - skb->nh.raw); | ||
294 | ahp->icv(ahp, skb, ah->auth_data); | ||
295 | if (memcmp(ah->auth_data, auth_data, ahp->icv_trunc_len)) { | ||
296 | LIMIT_NETDEBUG( | ||
297 | printk(KERN_WARNING "ipsec ah authentication error\n")); | ||
298 | x->stats.integrity_failed++; | ||
299 | goto free_out; | ||
300 | } | ||
301 | } | ||
302 | |||
303 | skb->nh.raw = skb_pull(skb, ah_hlen); | ||
304 | memcpy(skb->nh.raw, tmp_hdr, hdr_len); | ||
305 | skb->nh.ipv6h->payload_len = htons(skb->len - sizeof(struct ipv6hdr)); | ||
306 | skb_pull(skb, hdr_len); | ||
307 | skb->h.raw = skb->data; | ||
308 | |||
309 | |||
310 | kfree(tmp_hdr); | ||
311 | |||
312 | return nexthdr; | ||
313 | |||
314 | free_out: | ||
315 | kfree(tmp_hdr); | ||
316 | out: | ||
317 | return -EINVAL; | ||
318 | } | ||
319 | |||
320 | static void ah6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, | ||
321 | int type, int code, int offset, __u32 info) | ||
322 | { | ||
323 | struct ipv6hdr *iph = (struct ipv6hdr*)skb->data; | ||
324 | struct ip_auth_hdr *ah = (struct ip_auth_hdr*)(skb->data+offset); | ||
325 | struct xfrm_state *x; | ||
326 | |||
327 | if (type != ICMPV6_DEST_UNREACH && | ||
328 | type != ICMPV6_PKT_TOOBIG) | ||
329 | return; | ||
330 | |||
331 | x = xfrm_state_lookup((xfrm_address_t *)&iph->daddr, ah->spi, IPPROTO_AH, AF_INET6); | ||
332 | if (!x) | ||
333 | return; | ||
334 | |||
335 | NETDEBUG(printk(KERN_DEBUG "pmtu discovery on SA AH/%08x/" | ||
336 | "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n", | ||
337 | ntohl(ah->spi), NIP6(iph->daddr))); | ||
338 | |||
339 | xfrm_state_put(x); | ||
340 | } | ||
341 | |||
342 | static int ah6_init_state(struct xfrm_state *x, void *args) | ||
343 | { | ||
344 | struct ah_data *ahp = NULL; | ||
345 | struct xfrm_algo_desc *aalg_desc; | ||
346 | |||
347 | if (!x->aalg) | ||
348 | goto error; | ||
349 | |||
350 | /* null auth can use a zero length key */ | ||
351 | if (x->aalg->alg_key_len > 512) | ||
352 | goto error; | ||
353 | |||
354 | if (x->encap) | ||
355 | goto error; | ||
356 | |||
357 | ahp = kmalloc(sizeof(*ahp), GFP_KERNEL); | ||
358 | if (ahp == NULL) | ||
359 | return -ENOMEM; | ||
360 | |||
361 | memset(ahp, 0, sizeof(*ahp)); | ||
362 | |||
363 | ahp->key = x->aalg->alg_key; | ||
364 | ahp->key_len = (x->aalg->alg_key_len+7)/8; | ||
365 | ahp->tfm = crypto_alloc_tfm(x->aalg->alg_name, 0); | ||
366 | if (!ahp->tfm) | ||
367 | goto error; | ||
368 | ahp->icv = ah_hmac_digest; | ||
369 | |||
370 | /* | ||
371 | * Lookup the algorithm description maintained by xfrm_algo, | ||
372 | * verify crypto transform properties, and store information | ||
373 | * we need for AH processing. This lookup cannot fail here | ||
374 | * after a successful crypto_alloc_tfm(). | ||
375 | */ | ||
376 | aalg_desc = xfrm_aalg_get_byname(x->aalg->alg_name, 0); | ||
377 | BUG_ON(!aalg_desc); | ||
378 | |||
379 | if (aalg_desc->uinfo.auth.icv_fullbits/8 != | ||
380 | crypto_tfm_alg_digestsize(ahp->tfm)) { | ||
381 | printk(KERN_INFO "AH: %s digestsize %u != %hu\n", | ||
382 | x->aalg->alg_name, crypto_tfm_alg_digestsize(ahp->tfm), | ||
383 | aalg_desc->uinfo.auth.icv_fullbits/8); | ||
384 | goto error; | ||
385 | } | ||
386 | |||
387 | ahp->icv_full_len = aalg_desc->uinfo.auth.icv_fullbits/8; | ||
388 | ahp->icv_trunc_len = aalg_desc->uinfo.auth.icv_truncbits/8; | ||
389 | |||
390 | BUG_ON(ahp->icv_trunc_len > MAX_AH_AUTH_LEN); | ||
391 | |||
392 | ahp->work_icv = kmalloc(ahp->icv_full_len, GFP_KERNEL); | ||
393 | if (!ahp->work_icv) | ||
394 | goto error; | ||
395 | |||
396 | x->props.header_len = XFRM_ALIGN8(sizeof(struct ipv6_auth_hdr) + ahp->icv_trunc_len); | ||
397 | if (x->props.mode) | ||
398 | x->props.header_len += sizeof(struct ipv6hdr); | ||
399 | x->data = ahp; | ||
400 | |||
401 | return 0; | ||
402 | |||
403 | error: | ||
404 | if (ahp) { | ||
405 | if (ahp->work_icv) | ||
406 | kfree(ahp->work_icv); | ||
407 | if (ahp->tfm) | ||
408 | crypto_free_tfm(ahp->tfm); | ||
409 | kfree(ahp); | ||
410 | } | ||
411 | return -EINVAL; | ||
412 | } | ||
413 | |||
414 | static void ah6_destroy(struct xfrm_state *x) | ||
415 | { | ||
416 | struct ah_data *ahp = x->data; | ||
417 | |||
418 | if (!ahp) | ||
419 | return; | ||
420 | |||
421 | if (ahp->work_icv) { | ||
422 | kfree(ahp->work_icv); | ||
423 | ahp->work_icv = NULL; | ||
424 | } | ||
425 | if (ahp->tfm) { | ||
426 | crypto_free_tfm(ahp->tfm); | ||
427 | ahp->tfm = NULL; | ||
428 | } | ||
429 | kfree(ahp); | ||
430 | } | ||
431 | |||
432 | static struct xfrm_type ah6_type = | ||
433 | { | ||
434 | .description = "AH6", | ||
435 | .owner = THIS_MODULE, | ||
436 | .proto = IPPROTO_AH, | ||
437 | .init_state = ah6_init_state, | ||
438 | .destructor = ah6_destroy, | ||
439 | .input = ah6_input, | ||
440 | .output = ah6_output | ||
441 | }; | ||
442 | |||
443 | static struct inet6_protocol ah6_protocol = { | ||
444 | .handler = xfrm6_rcv, | ||
445 | .err_handler = ah6_err, | ||
446 | .flags = INET6_PROTO_NOPOLICY, | ||
447 | }; | ||
448 | |||
449 | static int __init ah6_init(void) | ||
450 | { | ||
451 | if (xfrm_register_type(&ah6_type, AF_INET6) < 0) { | ||
452 | printk(KERN_INFO "ipv6 ah init: can't add xfrm type\n"); | ||
453 | return -EAGAIN; | ||
454 | } | ||
455 | |||
456 | if (inet6_add_protocol(&ah6_protocol, IPPROTO_AH) < 0) { | ||
457 | printk(KERN_INFO "ipv6 ah init: can't add protocol\n"); | ||
458 | xfrm_unregister_type(&ah6_type, AF_INET6); | ||
459 | return -EAGAIN; | ||
460 | } | ||
461 | |||
462 | return 0; | ||
463 | } | ||
464 | |||
465 | static void __exit ah6_fini(void) | ||
466 | { | ||
467 | if (inet6_del_protocol(&ah6_protocol, IPPROTO_AH) < 0) | ||
468 | printk(KERN_INFO "ipv6 ah close: can't remove protocol\n"); | ||
469 | |||
470 | if (xfrm_unregister_type(&ah6_type, AF_INET6) < 0) | ||
471 | printk(KERN_INFO "ipv6 ah close: can't remove xfrm type\n"); | ||
472 | |||
473 | } | ||
474 | |||
475 | module_init(ah6_init); | ||
476 | module_exit(ah6_fini); | ||
477 | |||
478 | MODULE_LICENSE("GPL"); | ||