segmentless smp fixes
[minix3.git] / lib / libc / net / ip6opt.c
blobf7a9380f3237f37817c739554050fa7dbe0dd171
1 /* $NetBSD: ip6opt.c,v 1.12 2009/01/30 23:43:30 lukem Exp $ */
3 /*
4 * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the project nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
32 #include <sys/cdefs.h>
33 #if defined(LIBC_SCCS) && !defined(lint)
34 __RCSID("$NetBSD: ip6opt.c,v 1.12 2009/01/30 23:43:30 lukem Exp $");
35 #endif /* LIBC_SCCS and not lint */
37 #include "namespace.h"
38 #include <sys/param.h>
39 #include <sys/types.h>
40 #include <sys/socket.h>
42 #include <netinet/in.h>
43 #include <netinet/ip6.h>
45 #include <assert.h>
46 #include <string.h>
47 #include <stdio.h>
49 #ifdef __weak_alias
50 __weak_alias(inet6_option_alloc,_inet6_option_alloc)
51 __weak_alias(inet6_option_append,_inet6_option_append)
52 __weak_alias(inet6_option_find,_inet6_option_find)
53 __weak_alias(inet6_option_init,_inet6_option_init)
54 __weak_alias(inet6_option_next,_inet6_option_next)
55 __weak_alias(inet6_option_space,_inet6_option_space)
56 __weak_alias(inet6_opt_init, _inet6_opt_init)
57 __weak_alias(inet6_opt_append, _inet6_opt_append)
58 __weak_alias(inet6_opt_finish, _inet6_opt_finish)
59 __weak_alias(inet6_opt_set_val, _inet6_opt_set_val)
60 __weak_alias(inet6_opt_next, _inet6_opt_next)
61 __weak_alias(inet6_opt_find, _inet6_opt_find)
62 __weak_alias(inet6_opt_get_val, _inet6_opt_get_val)
63 #endif
65 static int ip6optlen(u_int8_t *opt, u_int8_t *lim);
66 static void inet6_insert_padopt(u_char *p, size_t len);
69 * This function returns the number of bytes required to hold an option
70 * when it is stored as ancillary data, including the cmsghdr structure
71 * at the beginning, and any padding at the end (to make its size a
72 * multiple of 8 bytes). The argument is the size of the structure
73 * defining the option, which must include any pad bytes at the
74 * beginning (the value y in the alignment term "xn + y"), the type
75 * byte, the length byte, and the option data.
77 int
78 inet6_option_space(nbytes)
79 int nbytes;
81 nbytes += 2; /* we need space for nxt-hdr and length fields */
82 return(CMSG_SPACE((nbytes + 7) & ~7));
86 * This function is called once per ancillary data object that will
87 * contain either Hop-by-Hop or Destination options. It returns 0 on
88 * success or -1 on an error.
90 int
91 inet6_option_init(bp, cmsgp, type)
92 void *bp;
93 struct cmsghdr **cmsgp;
94 int type;
96 register struct cmsghdr *ch;
98 _DIAGASSERT(bp != NULL);
99 _DIAGASSERT(cmsgp != NULL);
101 ch = (struct cmsghdr *)bp;
103 /* argument validation */
104 if (type != IPV6_HOPOPTS && type != IPV6_DSTOPTS)
105 return(-1);
107 ch->cmsg_level = IPPROTO_IPV6;
108 ch->cmsg_type = type;
109 ch->cmsg_len = CMSG_LEN(0);
111 *cmsgp = ch;
112 return(0);
116 * This function appends a Hop-by-Hop option or a Destination option
117 * into an ancillary data object that has been initialized by
118 * inet6_option_init(). This function returns 0 if it succeeds or -1 on
119 * an error.
120 * multx is the value x in the alignment term "xn + y" described
121 * earlier. It must have a value of 1, 2, 4, or 8.
122 * plusy is the value y in the alignment term "xn + y" described
123 * earlier. It must have a value between 0 and 7, inclusive.
126 inet6_option_append(cmsg, typep, multx, plusy)
127 struct cmsghdr *cmsg;
128 const u_int8_t *typep;
129 int multx;
130 int plusy;
132 size_t padlen, optlen, off;
133 register u_char *bp;
134 struct ip6_ext *eh;
136 _DIAGASSERT(cmsg != NULL);
137 _DIAGASSERT(typep != NULL);
139 bp = (u_char *)(void *)cmsg + cmsg->cmsg_len;
140 eh = (struct ip6_ext *)(void *)CMSG_DATA(cmsg);
142 /* argument validation */
143 if (multx != 1 && multx != 2 && multx != 4 && multx != 8)
144 return(-1);
145 if (plusy < 0 || plusy > 7)
146 return(-1);
149 * If this is the first option, allocate space for the
150 * first 2 bytes(for next header and length fields) of
151 * the option header.
153 if (bp == (u_char *)(void *)eh) {
154 bp += 2;
155 cmsg->cmsg_len += 2;
158 /* calculate pad length before the option. */
159 off = bp - (u_char *)(void *)eh;
160 padlen = (((off % multx) + (multx - 1)) & ~(multx - 1)) -
161 (off % multx);
162 padlen += plusy;
163 padlen %= multx; /* keep the pad as short as possible */
164 /* insert padding */
165 inet6_insert_padopt(bp, padlen);
166 cmsg->cmsg_len += padlen;
167 bp += padlen;
169 /* copy the option */
170 if (typep[0] == IP6OPT_PAD1)
171 optlen = 1;
172 else
173 optlen = typep[1] + 2;
174 memcpy(bp, typep, (size_t)optlen);
175 bp += optlen;
176 cmsg->cmsg_len += optlen;
178 /* calculate pad length after the option and insert the padding */
179 off = bp - (u_char *)(void *)eh;
180 padlen = ((off + 7) & ~7) - off;
181 inet6_insert_padopt(bp, padlen);
182 bp += padlen;
183 cmsg->cmsg_len += padlen;
185 /* update the length field of the ip6 option header */
186 off = bp - (u_char *)(void *)eh;
187 eh->ip6e_len = (off >> 3) - 1;
189 return(0);
193 * This function appends a Hop-by-Hop option or a Destination option
194 * into an ancillary data object that has been initialized by
195 * inet6_option_init(). This function returns a pointer to the 8-bit
196 * option type field that starts the option on success, or NULL on an
197 * error.
198 * The difference between this function and inet6_option_append() is
199 * that the latter copies the contents of a previously built option into
200 * the ancillary data object while the current function returns a
201 * pointer to the space in the data object where the option's TLV must
202 * then be built by the caller.
205 u_int8_t *
206 inet6_option_alloc(cmsg, datalen, multx, plusy)
207 struct cmsghdr *cmsg;
208 int datalen;
209 int multx;
210 int plusy;
212 size_t padlen, off;
213 register u_int8_t *bp;
214 u_int8_t *retval;
215 struct ip6_ext *eh;
217 _DIAGASSERT(cmsg != NULL);
219 bp = (u_char *)(void *)cmsg + cmsg->cmsg_len;
220 eh = (struct ip6_ext *)(void *)CMSG_DATA(cmsg);
222 /* argument validation */
223 if (multx != 1 && multx != 2 && multx != 4 && multx != 8)
224 return(NULL);
225 if (plusy < 0 || plusy > 7)
226 return(NULL);
229 * If this is the first option, allocate space for the
230 * first 2 bytes(for next header and length fields) of
231 * the option header.
233 if (bp == (u_char *)(void *)eh) {
234 bp += 2;
235 cmsg->cmsg_len += 2;
238 /* calculate pad length before the option. */
239 off = bp - (u_char *)(void *)eh;
240 padlen = (((off % multx) + (multx - 1)) & ~(multx - 1)) -
241 (off % multx);
242 padlen += plusy;
243 padlen %= multx; /* keep the pad as short as possible */
244 /* insert padding */
245 inet6_insert_padopt(bp, padlen);
246 cmsg->cmsg_len += padlen;
247 bp += padlen;
249 /* keep space to store specified length of data */
250 retval = bp;
251 bp += datalen;
252 cmsg->cmsg_len += datalen;
254 /* calculate pad length after the option and insert the padding */
255 off = bp - (u_char *)(void *)eh;
256 padlen = ((off + 7) & ~7) - off;
257 inet6_insert_padopt(bp, padlen);
258 bp += padlen;
259 cmsg->cmsg_len += padlen;
261 /* update the length field of the ip6 option header */
262 off = bp - (u_char *)(void *)eh;
263 eh->ip6e_len = (off >> 3) - 1;
265 return(retval);
269 * This function processes the next Hop-by-Hop option or Destination
270 * option in an ancillary data object. If another option remains to be
271 * processed, the return value of the function is 0 and *tptrp points to
272 * the 8-bit option type field (which is followed by the 8-bit option
273 * data length, followed by the option data). If no more options remain
274 * to be processed, the return value is -1 and *tptrp is NULL. If an
275 * error occurs, the return value is -1 and *tptrp is not NULL.
276 * (RFC 2292, 6.3.5)
279 inet6_option_next(cmsg, tptrp)
280 const struct cmsghdr *cmsg;
281 u_int8_t **tptrp;
283 struct ip6_ext *ip6e;
284 int hdrlen, optlen;
285 u_int8_t *lim;
287 _DIAGASSERT(cmsg != NULL);
288 _DIAGASSERT(tptrp != NULL);
290 if (cmsg->cmsg_level != IPPROTO_IPV6 ||
291 (cmsg->cmsg_type != IPV6_HOPOPTS &&
292 cmsg->cmsg_type != IPV6_DSTOPTS))
293 return(-1);
295 /* message length validation */
296 if (cmsg->cmsg_len < CMSG_SPACE(sizeof(struct ip6_ext)))
297 return(-1);
298 ip6e = __UNCONST(CCMSG_DATA(cmsg));
299 hdrlen = (ip6e->ip6e_len + 1) << 3;
300 if (cmsg->cmsg_len < CMSG_SPACE(hdrlen))
301 return(-1);
304 * If the caller does not specify the starting point,
305 * simply return the 1st option.
306 * Otherwise, search the option list for the next option.
308 lim = (u_int8_t *)(void *)ip6e + hdrlen;
309 if (*tptrp == NULL)
310 *tptrp = (u_int8_t *)(void *)(ip6e + 1);
311 else {
312 if ((optlen = ip6optlen(*tptrp, lim)) == 0)
313 return(-1);
315 *tptrp = *tptrp + optlen;
317 if (*tptrp >= lim) { /* there is no option */
318 *tptrp = NULL;
319 return(-1);
322 * Finally, checks if the next option is safely stored in the
323 * cmsg data.
325 if (ip6optlen(*tptrp, lim) == 0)
326 return(-1);
327 else
328 return(0);
332 * This function is similar to the inet6_option_next() function,
333 * except this function lets the caller specify the option type to be
334 * searched for, instead of always returning the next option in the
335 * ancillary data object.
336 * Note: RFC 2292 says the type of tptrp is u_int8_t *, but we think
337 * it's a typo. The variable should be type of u_int8_t **.
340 inet6_option_find(cmsg, tptrp, type)
341 const struct cmsghdr *cmsg;
342 u_int8_t **tptrp;
343 int type;
345 struct ip6_ext *ip6e;
346 int hdrlen, optlen;
347 u_int8_t *optp, *lim;
349 _DIAGASSERT(cmsg != NULL);
350 _DIAGASSERT(tptrp != NULL);
352 if (cmsg->cmsg_level != IPPROTO_IPV6 ||
353 (cmsg->cmsg_type != IPV6_HOPOPTS &&
354 cmsg->cmsg_type != IPV6_DSTOPTS))
355 return(-1);
357 /* message length validation */
358 if (cmsg->cmsg_len < CMSG_SPACE(sizeof(struct ip6_ext)))
359 return(-1);
360 ip6e = __UNCONST(CCMSG_DATA(cmsg));
361 hdrlen = (ip6e->ip6e_len + 1) << 3;
362 if (cmsg->cmsg_len < CMSG_SPACE(hdrlen))
363 return(-1);
366 * If the caller does not specify the starting point,
367 * search from the beginning of the option list.
368 * Otherwise, search from *the next option* of the specified point.
370 lim = (u_int8_t *)(void *)ip6e + hdrlen;
371 if (*tptrp == NULL)
372 *tptrp = (u_int8_t *)(void *)(ip6e + 1);
373 else {
374 if ((optlen = ip6optlen(*tptrp, lim)) == 0)
375 return(-1);
377 *tptrp = *tptrp + optlen;
379 for (optp = *tptrp; optp < lim; optp += optlen) {
380 if (*optp == type) {
381 *tptrp = optp;
382 return(0);
384 if ((optlen = ip6optlen(optp, lim)) == 0)
385 return(-1);
388 /* search failed */
389 *tptrp = NULL;
390 return(-1);
394 * Calculate the length of a given IPv6 option. Also checks
395 * if the option is safely stored in user's buffer according to the
396 * calculated length and the limitation of the buffer.
398 static int
399 ip6optlen(opt, lim)
400 u_int8_t *opt, *lim;
402 int optlen;
404 _DIAGASSERT(opt != NULL);
405 _DIAGASSERT(lim != NULL);
407 if (*opt == IP6OPT_PAD1)
408 optlen = 1;
409 else {
410 /* is there enough space to store type and len? */
411 if (opt + 2 > lim)
412 return(0);
413 optlen = *(opt + 1) + 2;
415 if (opt + optlen <= lim)
416 return(optlen);
418 return(0);
421 static void
422 inet6_insert_padopt(u_char *p, size_t len)
425 _DIAGASSERT(p != NULL);
427 switch(len) {
428 case 0:
429 return;
430 case 1:
431 p[0] = IP6OPT_PAD1;
432 return;
433 default:
434 p[0] = IP6OPT_PADN;
435 p[1] = len - 2;
436 memset(&p[2], 0, len - 2);
437 return;
442 * The following functions are defined in RFC3542, which is a successor
443 * of RFC2292.
447 inet6_opt_init(void *extbuf, socklen_t extlen)
449 struct ip6_ext *ext = (struct ip6_ext *)extbuf;
451 if (extlen % 8)
452 return (-1);
454 if (ext) {
455 if (extlen == 0)
456 return (-1);
457 ext->ip6e_len = (extlen >> 3) - 1;
460 return (2); /* sizeof the next and the length fields */
464 inet6_opt_append(void *extbuf, socklen_t extlen, int offset, u_int8_t type,
465 socklen_t len, u_int8_t align, void **databufp)
467 int currentlen = offset;
468 size_t padlen = 0;
471 * The option type must have a value from 2 to 255, inclusive.
472 * (0 and 1 are reserved for the Pad1 and PadN options, respectively.)
474 if (type < 2)
475 return (-1);
478 * The option data length must have a value between 0 and 255,
479 * inclusive, and is the length of the option data that follows.
481 if (len > 255)
482 return (-1);
485 * The align parameter must have a value of 1, 2, 4, or 8.
486 * The align value can not exceed the value of len.
488 if (align != 1 && align != 2 && align != 4 && align != 8)
489 return (-1);
490 if (align > len)
491 return (-1);
493 /* Calculate the padding length. */
494 currentlen += 2 + len; /* 2 means "type + len" */
495 if (currentlen % align)
496 padlen = align - (currentlen % align);
498 /* The option must fit in the extension header buffer. */
499 currentlen += padlen;
500 if (extlen && /* XXX: right? */
501 (socklen_t)currentlen > extlen)
502 return (-1);
504 if (extbuf) {
505 u_int8_t *optp = (u_int8_t *)extbuf + offset;
507 if (padlen == 1) {
508 /* insert a Pad1 option */
509 *optp = IP6OPT_PAD1;
510 optp++;
511 } else if (padlen > 0) {
512 /* insert a PadN option for alignment */
513 *optp++ = IP6OPT_PADN;
514 *optp++ = padlen - 2;
515 memset(optp, 0, padlen - 2);
516 optp += (padlen - 2);
519 *optp++ = type;
520 *optp++ = len;
522 *databufp = optp;
525 return (currentlen);
529 inet6_opt_finish(void *extbuf, socklen_t extlen, int offset)
531 int updatelen = offset > 0 ? (1 + ((offset - 1) | 7)) : 0;
533 if (extbuf) {
534 u_int8_t *padp;
535 size_t padlen = updatelen - offset;
537 if ((socklen_t)updatelen > extlen)
538 return (-1);
540 padp = (u_int8_t *)extbuf + offset;
541 if (padlen == 1)
542 *padp = IP6OPT_PAD1;
543 else if (padlen > 0) {
544 *padp++ = IP6OPT_PADN;
545 *padp++ = (padlen - 2);
546 memset(padp, 0, padlen - 2);
550 return (updatelen);
554 inet6_opt_set_val(void *databuf, int offset, void *val, socklen_t vallen)
557 memcpy((u_int8_t *)databuf + offset, val, vallen);
558 return (offset + vallen);
562 inet6_opt_next(void *extbuf, socklen_t extlen, int offset, u_int8_t *typep,
563 socklen_t *lenp, void **databufp)
565 u_int8_t *optp, *lim;
566 int optlen;
568 /* Validate extlen. XXX: is the variable really necessary?? */
569 if (extlen == 0 || (extlen % 8))
570 return (-1);
571 lim = (u_int8_t *)extbuf + extlen;
574 * If this is the first time this function called for this options
575 * header, simply return the 1st option.
576 * Otherwise, search the option list for the next option.
578 if (offset == 0)
579 optp = (u_int8_t *)(void *)((struct ip6_hbh *)extbuf + 1);
580 else
581 optp = (u_int8_t *)extbuf + offset;
583 /* Find the next option skipping any padding options. */
584 while (optp < lim) {
585 switch(*optp) {
586 case IP6OPT_PAD1:
587 optp++;
588 break;
589 case IP6OPT_PADN:
590 if ((optlen = ip6optlen(optp, lim)) == 0)
591 goto optend;
592 optp += optlen;
593 break;
594 default: /* found */
595 if ((optlen = ip6optlen(optp, lim)) == 0)
596 goto optend;
597 *typep = *optp;
598 *lenp = optlen - 2;
599 *databufp = optp + 2;
600 return (optp + optlen - (u_int8_t *)extbuf);
604 optend:
605 *databufp = NULL; /* for safety */
606 return (-1);
610 inet6_opt_find(void *extbuf, socklen_t extlen, int offset, u_int8_t type,
611 socklen_t *lenp, void **databufp)
613 u_int8_t *optp, *lim;
614 int optlen;
616 /* Validate extlen. XXX: is the variable really necessary?? */
617 if (extlen == 0 || (extlen % 8))
618 return (-1);
619 lim = (u_int8_t *)extbuf + extlen;
622 * If this is the first time this function called for this options
623 * header, simply return the 1st option.
624 * Otherwise, search the option list for the next option.
626 if (offset == 0)
627 optp = (u_int8_t *)(void *)((struct ip6_hbh *)extbuf + 1);
628 else
629 optp = (u_int8_t *)extbuf + offset;
631 /* Find the specified option */
632 while (optp < lim) {
633 if ((optlen = ip6optlen(optp, lim)) == 0)
634 goto optend;
636 if (*optp == type) { /* found */
637 *lenp = optlen - 2;
638 *databufp = optp + 2;
639 return (optp + optlen - (u_int8_t *)extbuf);
642 optp += optlen;
645 optend:
646 *databufp = NULL; /* for safety */
647 return (-1);
651 inet6_opt_get_val(void *databuf, int offset, void *val, socklen_t vallen)
654 /* we can't assume alignment here */
655 memcpy(val, (u_int8_t *)databuf + offset, vallen);
657 return (offset + vallen);