Break paragraph with driver rewrite.
[netbsd-mini2440.git] / sys / netinet6 / ah_output.c
blobdf4c4dd648b611cc8355446acf5f9a6d82c6ae48
1 /* $NetBSD: ah_output.c,v 1.32 2009/03/14 14:46:10 dsl Exp $ */
2 /* $KAME: ah_output.c,v 1.31 2001/07/26 06:53:15 jinmei Exp $ */
4 /*
5 * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
6 * All rights reserved.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. Neither the name of the project nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
20 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
34 * RFC1826/2402 authentication header.
37 #include <sys/cdefs.h>
38 __KERNEL_RCSID(0, "$NetBSD: ah_output.c,v 1.32 2009/03/14 14:46:10 dsl Exp $");
40 #include "opt_inet.h"
42 #include <sys/param.h>
43 #include <sys/systm.h>
44 #include <sys/malloc.h>
45 #include <sys/mbuf.h>
46 #include <sys/domain.h>
47 #include <sys/protosw.h>
48 #include <sys/socket.h>
49 #include <sys/socketvar.h>
50 #include <sys/errno.h>
51 #include <sys/time.h>
52 #include <sys/kernel.h>
53 #include <sys/syslog.h>
55 #include <net/if.h>
56 #include <net/route.h>
58 #include <netinet/in.h>
60 #include <netinet/in_systm.h>
61 #include <netinet/ip.h>
62 #include <netinet/in_var.h>
64 #ifdef INET6
65 #include <netinet/ip6.h>
66 #include <netinet6/ip6_var.h>
67 #include <netinet/icmp6.h>
68 #endif
70 #include <netinet6/ipsec.h>
71 #include <netinet6/ipsec_private.h>
72 #include <netinet6/ah.h>
73 #include <netkey/key.h>
74 #include <netkey/keydb.h>
76 #include <net/net_osdep.h>
78 #ifdef INET
79 static struct in_addr *ah4_finaldst(struct mbuf *);
80 #endif
83 * compute AH header size.
84 * transport mode only. for tunnel mode, we should implement
85 * virtual interface, and control MTU/MSS by the interface MTU.
87 size_t
88 ah_hdrsiz(struct ipsecrequest *isr)
90 const struct ah_algorithm *algo;
91 size_t hdrsiz;
93 /* sanity check */
94 if (isr == NULL)
95 panic("ah_hdrsiz: NULL was passed.");
97 if (isr->saidx.proto != IPPROTO_AH)
98 panic("unsupported mode passed to ah_hdrsiz");
100 if (isr->sav == NULL)
101 goto estimate;
102 if (isr->sav->state != SADB_SASTATE_MATURE
103 && isr->sav->state != SADB_SASTATE_DYING)
104 goto estimate;
106 /* we need transport mode AH. */
107 algo = ah_algorithm_lookup(isr->sav->alg_auth);
108 if (!algo)
109 goto estimate;
112 * XXX
113 * right now we don't calcurate the padding size. simply
114 * treat the padding size as constant, for simplicity.
116 * XXX variable size padding support
118 hdrsiz = (((*algo->sumsiz)(isr->sav) + 3) & ~(4 - 1));
119 if (isr->sav->flags & SADB_X_EXT_OLD)
120 hdrsiz += sizeof(struct ah);
121 else
122 hdrsiz += sizeof(struct newah);
124 return hdrsiz;
126 estimate:
127 /* ASSUMING:
128 * sizeof(struct newah) > sizeof(struct ah).
129 * AH_MAXSUMSIZE is multiple of 4.
131 return sizeof(struct newah) + AH_MAXSUMSIZE;
134 #ifdef INET
136 * Modify the packet so that it includes the authentication data.
137 * The mbuf passed must start with IPv4 header.
139 * assumes that the first mbuf contains IPv4 header + option only.
140 * the function does not modify m.
143 ah4_output(struct mbuf *m, struct ipsecrequest *isr)
145 struct secasvar *sav = isr->sav;
146 const struct ah_algorithm *algo;
147 u_int32_t spi;
148 u_char *ahdrpos;
149 u_int8_t *ahsumpos = NULL;
150 size_t hlen = 0; /* IP header+option in bytes */
151 size_t plen = 0; /* AH payload size in bytes */
152 size_t ahlen = 0; /* plen + sizeof(ah) */
153 struct ip *ip;
154 struct in_addr dst;
155 struct in_addr *finaldst;
156 int error;
157 dst.s_addr = 0; /* XXX: GCC */
159 /* sanity checks */
160 if ((sav->flags & SADB_X_EXT_OLD) == 0 && !sav->replay) {
161 ip = mtod(m, struct ip *);
162 ipseclog((LOG_DEBUG, "ah4_output: internal error: "
163 "sav->replay is null: %x->%x, SPI=%u\n",
164 (u_int32_t)ntohl(ip->ip_src.s_addr),
165 (u_int32_t)ntohl(ip->ip_dst.s_addr),
166 (u_int32_t)ntohl(sav->spi)));
167 IPSEC_STATINC(IPSEC_STAT_OUT_INVAL);
168 error = EINVAL;
169 goto fail;
172 algo = ah_algorithm_lookup(sav->alg_auth);
173 if (!algo) {
174 ipseclog((LOG_ERR, "ah4_output: unsupported algorithm: "
175 "SPI=%u\n", (u_int32_t)ntohl(sav->spi)));
176 IPSEC_STATINC(IPSEC_STAT_OUT_INVAL);
177 error = EINVAL;
178 goto fail;
180 spi = sav->spi;
183 * determine the size to grow.
185 if (sav->flags & SADB_X_EXT_OLD) {
186 /* RFC 1826 */
187 plen = ((*algo->sumsiz)(sav) + 3) & ~(4 - 1); /* XXX pad to 8byte? */
188 ahlen = plen + sizeof(struct ah);
189 } else {
190 /* RFC 2402 */
191 plen = ((*algo->sumsiz)(sav) + 3) & ~(4 - 1); /* XXX pad to 8byte? */
192 ahlen = plen + sizeof(struct newah);
196 * grow the mbuf to accommodate AH.
198 ip = mtod(m, struct ip *);
199 hlen = ip->ip_hl << 2;
201 if (m->m_len != hlen)
202 panic("ah4_output: assumption failed (first mbuf length)");
203 if (M_LEADINGSPACE(m->m_next) < ahlen) {
204 struct mbuf *n;
205 MGET(n, M_DONTWAIT, MT_DATA);
206 if (!n) {
207 ipseclog((LOG_DEBUG, "ENOBUFS in ah4_output %d\n",
208 __LINE__));
209 error = ENOBUFS;
210 goto fail;
212 n->m_len = ahlen;
213 n->m_next = m->m_next;
214 m->m_next = n;
215 m->m_pkthdr.len += ahlen;
216 ahdrpos = mtod(n, u_char *);
217 } else {
218 m->m_next->m_len += ahlen;
219 m->m_next->m_data -= ahlen;
220 m->m_pkthdr.len += ahlen;
221 ahdrpos = mtod(m->m_next, u_char *);
224 ip = mtod(m, struct ip *); /* just to be sure */
227 * initialize AH.
229 if (sav->flags & SADB_X_EXT_OLD) {
230 struct ah *ahdr;
232 ahdr = (struct ah *)ahdrpos;
233 ahsumpos = (u_char *)(ahdr + 1);
234 ahdr->ah_len = plen >> 2;
235 ahdr->ah_nxt = ip->ip_p;
236 ahdr->ah_reserve = htons(0);
237 ahdr->ah_spi = spi;
238 memset(ahdr + 1, 0, plen);
239 } else {
240 struct newah *ahdr;
242 ahdr = (struct newah *)ahdrpos;
243 ahsumpos = (u_char *)(ahdr + 1);
244 ahdr->ah_len = (plen >> 2) + 1; /* plus one for seq# */
245 ahdr->ah_nxt = ip->ip_p;
246 ahdr->ah_reserve = htons(0);
247 ahdr->ah_spi = spi;
248 if (sav->replay->count == ~0) {
249 if ((sav->flags & SADB_X_EXT_CYCSEQ) == 0) {
250 /* XXX Is it noisy ? */
251 ipseclog((LOG_WARNING,
252 "replay counter overflowed. %s\n",
253 ipsec_logsastr(sav)));
254 IPSEC_STATINC(IPSEC_STAT_OUT_INVAL);
255 error = EINVAL;
256 goto fail;
259 sav->replay->count++;
261 * XXX sequence number must not be cycled, if the SA is
262 * installed by IKE daemon.
264 ahdr->ah_seq = htonl(sav->replay->count & 0xffffffff);
265 memset(ahdr + 1, 0, plen);
269 * modify IPv4 header.
271 ip->ip_p = IPPROTO_AH;
272 if (ahlen < (IP_MAXPACKET - ntohs(ip->ip_len)))
273 ip->ip_len = htons(ntohs(ip->ip_len) + ahlen);
274 else {
275 ipseclog((LOG_ERR, "IPv4 AH output: size exceeds limit\n"));
276 IPSEC_STATINC(IPSEC_STAT_OUT_INVAL);
277 error = EMSGSIZE;
278 goto fail;
282 * If there is source routing option, update destination field in
283 * the IPv4 header to the final destination.
284 * Note that we do not need to update source routing option itself
285 * (as done in IPv4 AH processing -- see ip6_output()), since
286 * source routing option is not part of the ICV computation.
288 finaldst = ah4_finaldst(m);
289 if (finaldst) {
290 dst.s_addr = ip->ip_dst.s_addr;
291 ip->ip_dst.s_addr = finaldst->s_addr;
295 * calcurate the checksum, based on security association
296 * and the algorithm specified.
298 error = ah4_calccksum(m, ahsumpos, plen, algo, sav);
299 if (error) {
300 ipseclog((LOG_ERR,
301 "error after ah4_calccksum, called from ah4_output"));
302 IPSEC_STATINC(IPSEC_STAT_OUT_INVAL);
303 goto fail;
306 if (finaldst) {
307 ip = mtod(m, struct ip *); /* just to make sure */
308 ip->ip_dst.s_addr = dst.s_addr;
311 uint64_t *ipss = IPSEC_STAT_GETREF();
312 ipss[IPSEC_STAT_OUT_SUCCESS]++;
313 ipss[IPSEC_STAT_OUT_AHHIST + sav->alg_auth]++;
314 IPSEC_STAT_PUTREF();
316 key_sa_recordxfer(sav, m);
318 return 0;
320 fail:
321 m_freem(m);
322 return error;
324 #endif
326 /* Calculate AH length */
328 ah_hdrlen(struct secasvar *sav)
330 const struct ah_algorithm *algo;
331 int plen, ahlen;
333 algo = ah_algorithm_lookup(sav->alg_auth);
334 if (!algo)
335 return 0;
336 if (sav->flags & SADB_X_EXT_OLD) {
337 /* RFC 1826 */
338 plen = ((*algo->sumsiz)(sav) + 3) & ~(4 - 1); /* XXX pad to 8byte? */
339 ahlen = plen + sizeof(struct ah);
340 } else {
341 /* RFC 2402 */
342 plen = ((*algo->sumsiz)(sav) + 3) & ~(4 - 1); /* XXX pad to 8byte? */
343 ahlen = plen + sizeof(struct newah);
346 return (ahlen);
349 #ifdef INET6
351 * Fill in the Authentication Header and calculate checksum.
354 ah6_output(struct mbuf *m, u_char *nexthdrp, struct mbuf *md,
355 struct ipsecrequest *isr)
357 struct mbuf *mprev;
358 struct mbuf *mah;
359 struct secasvar *sav = isr->sav;
360 const struct ah_algorithm *algo;
361 u_int32_t spi;
362 u_int8_t *ahsumpos = NULL;
363 size_t plen; /* AH payload size in bytes */
364 int error = 0;
365 int ahlen;
366 struct ip6_hdr *ip6;
368 if (m->m_len < sizeof(struct ip6_hdr)) {
369 ipseclog((LOG_DEBUG, "ah6_output: first mbuf too short\n"));
370 error = EINVAL;
371 goto fail;
374 ahlen = ah_hdrlen(sav);
375 if (ahlen == 0)
376 return 0;
378 for (mprev = m; mprev && mprev->m_next != md; mprev = mprev->m_next)
380 if (!mprev || mprev->m_next != md) {
381 ipseclog((LOG_DEBUG, "ah6_output: md is not in chain\n"));
382 error = EINVAL;
383 goto fail;
386 MGET(mah, M_DONTWAIT, MT_DATA);
387 if (!mah) {
388 error = ENOBUFS;
389 goto fail;
391 if (ahlen > MLEN) {
392 MCLGET(mah, M_DONTWAIT);
393 if ((mah->m_flags & M_EXT) == 0) {
394 m_free(mah);
395 error = ENOBUFS;
396 goto fail;
399 mah->m_len = ahlen;
400 mah->m_next = md;
401 mprev->m_next = mah;
402 m->m_pkthdr.len += ahlen;
404 /* fix plen */
405 if (m->m_pkthdr.len - sizeof(struct ip6_hdr) > IPV6_MAXPACKET) {
406 ipseclog((LOG_ERR,
407 "ah6_output: AH with IPv6 jumbogram is not supported\n"));
408 error = EINVAL;
409 goto fail;
411 ip6 = mtod(m, struct ip6_hdr *);
412 ip6->ip6_plen = htons(m->m_pkthdr.len - sizeof(struct ip6_hdr));
414 if ((sav->flags & SADB_X_EXT_OLD) == 0 && !sav->replay) {
415 ipseclog((LOG_DEBUG, "ah6_output: internal error: "
416 "sav->replay is null: SPI=%u\n",
417 (u_int32_t)ntohl(sav->spi)));
418 IPSEC6_STATINC(IPSEC_STAT_OUT_INVAL);
419 error = EINVAL;
420 goto fail;
423 algo = ah_algorithm_lookup(sav->alg_auth);
424 if (!algo) {
425 ipseclog((LOG_ERR, "ah6_output: unsupported algorithm: "
426 "SPI=%u\n", (u_int32_t)ntohl(sav->spi)));
427 IPSEC6_STATINC(IPSEC_STAT_OUT_INVAL);
428 error = EINVAL;
429 goto fail;
431 spi = sav->spi;
434 * initialize AH.
436 if (sav->flags & SADB_X_EXT_OLD) {
437 struct ah *ahdr = mtod(mah, struct ah *);
439 plen = mah->m_len - sizeof(struct ah);
440 ahsumpos = (u_char *)(ahdr + 1);
441 ahdr->ah_nxt = *nexthdrp;
442 *nexthdrp = IPPROTO_AH;
443 ahdr->ah_len = plen >> 2;
444 ahdr->ah_reserve = htons(0);
445 ahdr->ah_spi = spi;
446 memset(ahdr + 1, 0, plen);
447 } else {
448 struct newah *ahdr = mtod(mah, struct newah *);
450 plen = mah->m_len - sizeof(struct newah);
451 ahsumpos = (u_char *)(ahdr + 1);
452 ahdr->ah_nxt = *nexthdrp;
453 *nexthdrp = IPPROTO_AH;
454 ahdr->ah_len = (plen >> 2) + 1; /* plus one for seq# */
455 ahdr->ah_reserve = htons(0);
456 ahdr->ah_spi = spi;
457 if (sav->replay->count == ~0) {
458 if ((sav->flags & SADB_X_EXT_CYCSEQ) == 0) {
459 /* XXX Is it noisy ? */
460 ipseclog((LOG_WARNING,
461 "replay counter overflowed. %s\n",
462 ipsec_logsastr(sav)));
463 IPSEC6_STATINC(IPSEC_STAT_OUT_INVAL);
464 error = EINVAL;
465 goto fail;
468 sav->replay->count++;
470 * XXX sequence number must not be cycled, if the SA is
471 * installed by IKE daemon.
473 ahdr->ah_seq = htonl(sav->replay->count);
474 memset(ahdr + 1, 0, plen);
478 * calcurate the checksum, based on security association
479 * and the algorithm specified.
481 error = ah6_calccksum(m, ahsumpos, plen, algo, sav);
482 if (error) {
483 IPSEC6_STATINC(IPSEC_STAT_OUT_INVAL);
484 goto fail;
485 } else {
486 IPSEC6_STATINC(IPSEC_STAT_OUT_SUCCESS);
487 key_sa_recordxfer(sav, m);
489 IPSEC6_STATINC(IPSEC_STAT_OUT_AHHIST + sav->alg_auth);
491 return 0;
493 fail:
494 m_freem(m);
495 return error;
498 #endif
500 #ifdef INET
502 * Find the final destination if there is loose/strict source routing option.
503 * Returns NULL if there's no source routing options.
504 * Returns NULL on errors too.
505 * Note that this function will return a pointer INTO the given parameter,
506 * struct mbuf *m.
507 * The mbuf must be pulled up toward, at least, ip option part.
509 static struct in_addr *
510 ah4_finaldst(struct mbuf *m)
512 struct ip *ip;
513 int optlen;
514 u_char *q;
515 int i;
516 int hlen;
518 if (!m)
519 panic("ah4_finaldst: m == NULL");
520 ip = mtod(m, struct ip *);
521 hlen = (ip->ip_hl << 2);
523 if (m->m_len < hlen) {
524 ipseclog((LOG_DEBUG,
525 "ah4_finaldst: parameter mbuf wrong (not pulled up)\n"));
526 return NULL;
529 if (hlen == sizeof(struct ip))
530 return NULL;
532 optlen = hlen - sizeof(struct ip);
533 if (optlen < 0) {
534 ipseclog((LOG_DEBUG, "ah4_finaldst: wrong optlen %d\n",
535 optlen));
536 return NULL;
539 q = (u_char *)(ip + 1);
540 i = 0;
541 while (i < optlen) {
542 if (i + IPOPT_OPTVAL >= optlen)
543 return NULL;
544 if (q[i + IPOPT_OPTVAL] == IPOPT_EOL ||
545 q[i + IPOPT_OPTVAL] == IPOPT_NOP ||
546 i + IPOPT_OLEN < optlen)
548 else
549 return NULL;
551 switch (q[i + IPOPT_OPTVAL]) {
552 case IPOPT_EOL:
553 i = optlen; /* bye */
554 break;
555 case IPOPT_NOP:
556 i++;
557 break;
558 case IPOPT_LSRR:
559 case IPOPT_SSRR:
560 if (q[i + IPOPT_OLEN] < 2 + sizeof(struct in_addr) ||
561 optlen - i < q[i + IPOPT_OLEN]) {
562 ipseclog((LOG_ERR,
563 "ip_finaldst: invalid IP option "
564 "(code=%02x len=%02x)\n",
565 q[i + IPOPT_OPTVAL], q[i + IPOPT_OLEN]));
566 return NULL;
568 i += q[i + IPOPT_OLEN] - sizeof(struct in_addr);
569 return (struct in_addr *)(q + i);
570 default:
571 if (q[i + IPOPT_OLEN] < 2 ||
572 optlen - i < q[i + IPOPT_OLEN]) {
573 ipseclog((LOG_ERR,
574 "ip_finaldst: invalid IP option "
575 "(code=%02x len=%02x)\n",
576 q[i + IPOPT_OPTVAL], q[i + IPOPT_OLEN]));
577 return NULL;
579 i += q[i + IPOPT_OLEN];
580 break;
583 return NULL;
585 #endif