ip6opt.c revision 1.12 1 /* $NetBSD: ip6opt.c,v 1.12 2009/01/30 23:43:30 lukem Exp $ */
2
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.
18 *
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.
30 */
31
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 */
36
37 #include "namespace.h"
38 #include <sys/param.h>
39 #include <sys/types.h>
40 #include <sys/socket.h>
41
42 #include <netinet/in.h>
43 #include <netinet/ip6.h>
44
45 #include <assert.h>
46 #include <string.h>
47 #include <stdio.h>
48
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
64
65 static int ip6optlen(u_int8_t *opt, u_int8_t *lim);
66 static void inet6_insert_padopt(u_char *p, size_t len);
67
68 /*
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.
76 */
77 int
78 inet6_option_space(nbytes)
79 int nbytes;
80 {
81 nbytes += 2; /* we need space for nxt-hdr and length fields */
82 return(CMSG_SPACE((nbytes + 7) & ~7));
83 }
84
85 /*
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.
89 */
90 int
91 inet6_option_init(bp, cmsgp, type)
92 void *bp;
93 struct cmsghdr **cmsgp;
94 int type;
95 {
96 register struct cmsghdr *ch;
97
98 _DIAGASSERT(bp != NULL);
99 _DIAGASSERT(cmsgp != NULL);
100
101 ch = (struct cmsghdr *)bp;
102
103 /* argument validation */
104 if (type != IPV6_HOPOPTS && type != IPV6_DSTOPTS)
105 return(-1);
106
107 ch->cmsg_level = IPPROTO_IPV6;
108 ch->cmsg_type = type;
109 ch->cmsg_len = CMSG_LEN(0);
110
111 *cmsgp = ch;
112 return(0);
113 }
114
115 /*
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.
124 */
125 int
126 inet6_option_append(cmsg, typep, multx, plusy)
127 struct cmsghdr *cmsg;
128 const u_int8_t *typep;
129 int multx;
130 int plusy;
131 {
132 size_t padlen, optlen, off;
133 register u_char *bp;
134 struct ip6_ext *eh;
135
136 _DIAGASSERT(cmsg != NULL);
137 _DIAGASSERT(typep != NULL);
138
139 bp = (u_char *)(void *)cmsg + cmsg->cmsg_len;
140 eh = (struct ip6_ext *)(void *)CMSG_DATA(cmsg);
141
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);
147
148 /*
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.
152 */
153 if (bp == (u_char *)(void *)eh) {
154 bp += 2;
155 cmsg->cmsg_len += 2;
156 }
157
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;
168
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;
177
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;
184
185 /* update the length field of the ip6 option header */
186 off = bp - (u_char *)(void *)eh;
187 eh->ip6e_len = (off >> 3) - 1;
188
189 return(0);
190 }
191
192 /*
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.
203 *
204 */
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;
211 {
212 size_t padlen, off;
213 register u_int8_t *bp;
214 u_int8_t *retval;
215 struct ip6_ext *eh;
216
217 _DIAGASSERT(cmsg != NULL);
218
219 bp = (u_char *)(void *)cmsg + cmsg->cmsg_len;
220 eh = (struct ip6_ext *)(void *)CMSG_DATA(cmsg);
221
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);
227
228 /*
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.
232 */
233 if (bp == (u_char *)(void *)eh) {
234 bp += 2;
235 cmsg->cmsg_len += 2;
236 }
237
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;
248
249 /* keep space to store specified length of data */
250 retval = bp;
251 bp += datalen;
252 cmsg->cmsg_len += datalen;
253
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;
260
261 /* update the length field of the ip6 option header */
262 off = bp - (u_char *)(void *)eh;
263 eh->ip6e_len = (off >> 3) - 1;
264
265 return(retval);
266 }
267
268 /*
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)
277 */
278 int
279 inet6_option_next(cmsg, tptrp)
280 const struct cmsghdr *cmsg;
281 u_int8_t **tptrp;
282 {
283 struct ip6_ext *ip6e;
284 int hdrlen, optlen;
285 u_int8_t *lim;
286
287 _DIAGASSERT(cmsg != NULL);
288 _DIAGASSERT(tptrp != NULL);
289
290 if (cmsg->cmsg_level != IPPROTO_IPV6 ||
291 (cmsg->cmsg_type != IPV6_HOPOPTS &&
292 cmsg->cmsg_type != IPV6_DSTOPTS))
293 return(-1);
294
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);
302
303 /*
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.
307 */
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);
314
315 *tptrp = *tptrp + optlen;
316 }
317 if (*tptrp >= lim) { /* there is no option */
318 *tptrp = NULL;
319 return(-1);
320 }
321 /*
322 * Finally, checks if the next option is safely stored in the
323 * cmsg data.
324 */
325 if (ip6optlen(*tptrp, lim) == 0)
326 return(-1);
327 else
328 return(0);
329 }
330
331 /*
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 **.
338 */
339 int
340 inet6_option_find(cmsg, tptrp, type)
341 const struct cmsghdr *cmsg;
342 u_int8_t **tptrp;
343 int type;
344 {
345 struct ip6_ext *ip6e;
346 int hdrlen, optlen;
347 u_int8_t *optp, *lim;
348
349 _DIAGASSERT(cmsg != NULL);
350 _DIAGASSERT(tptrp != NULL);
351
352 if (cmsg->cmsg_level != IPPROTO_IPV6 ||
353 (cmsg->cmsg_type != IPV6_HOPOPTS &&
354 cmsg->cmsg_type != IPV6_DSTOPTS))
355 return(-1);
356
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);
364
365 /*
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.
369 */
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);
376
377 *tptrp = *tptrp + optlen;
378 }
379 for (optp = *tptrp; optp < lim; optp += optlen) {
380 if (*optp == type) {
381 *tptrp = optp;
382 return(0);
383 }
384 if ((optlen = ip6optlen(optp, lim)) == 0)
385 return(-1);
386 }
387
388 /* search failed */
389 *tptrp = NULL;
390 return(-1);
391 }
392
393 /*
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.
397 */
398 static int
399 ip6optlen(opt, lim)
400 u_int8_t *opt, *lim;
401 {
402 int optlen;
403
404 _DIAGASSERT(opt != NULL);
405 _DIAGASSERT(lim != NULL);
406
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;
414 }
415 if (opt + optlen <= lim)
416 return(optlen);
417
418 return(0);
419 }
420
421 static void
422 inet6_insert_padopt(u_char *p, size_t len)
423 {
424
425 _DIAGASSERT(p != NULL);
426
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;
438 }
439 }
440
441 /*
442 * The following functions are defined in RFC3542, which is a successor
443 * of RFC2292.
444 */
445
446 int
447 inet6_opt_init(void *extbuf, socklen_t extlen)
448 {
449 struct ip6_ext *ext = (struct ip6_ext *)extbuf;
450
451 if (extlen % 8)
452 return (-1);
453
454 if (ext) {
455 if (extlen == 0)
456 return (-1);
457 ext->ip6e_len = (extlen >> 3) - 1;
458 }
459
460 return (2); /* sizeof the next and the length fields */
461 }
462
463 int
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)
466 {
467 int currentlen = offset;
468 size_t padlen = 0;
469
470 /*
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.)
473 */
474 if (type < 2)
475 return (-1);
476
477 /*
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.
480 */
481 if (len > 255)
482 return (-1);
483
484 /*
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.
487 */
488 if (align != 1 && align != 2 && align != 4 && align != 8)
489 return (-1);
490 if (align > len)
491 return (-1);
492
493 /* Calculate the padding length. */
494 currentlen += 2 + len; /* 2 means "type + len" */
495 if (currentlen % align)
496 padlen = align - (currentlen % align);
497
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);
503
504 if (extbuf) {
505 u_int8_t *optp = (u_int8_t *)extbuf + offset;
506
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);
517 }
518
519 *optp++ = type;
520 *optp++ = len;
521
522 *databufp = optp;
523 }
524
525 return (currentlen);
526 }
527
528 int
529 inet6_opt_finish(void *extbuf, socklen_t extlen, int offset)
530 {
531 int updatelen = offset > 0 ? (1 + ((offset - 1) | 7)) : 0;
532
533 if (extbuf) {
534 u_int8_t *padp;
535 size_t padlen = updatelen - offset;
536
537 if ((socklen_t)updatelen > extlen)
538 return (-1);
539
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);
547 }
548 }
549
550 return (updatelen);
551 }
552
553 int
554 inet6_opt_set_val(void *databuf, int offset, void *val, socklen_t vallen)
555 {
556
557 memcpy((u_int8_t *)databuf + offset, val, vallen);
558 return (offset + vallen);
559 }
560
561 int
562 inet6_opt_next(void *extbuf, socklen_t extlen, int offset, u_int8_t *typep,
563 socklen_t *lenp, void **databufp)
564 {
565 u_int8_t *optp, *lim;
566 int optlen;
567
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;
572
573 /*
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.
577 */
578 if (offset == 0)
579 optp = (u_int8_t *)(void *)((struct ip6_hbh *)extbuf + 1);
580 else
581 optp = (u_int8_t *)extbuf + offset;
582
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);
601 }
602 }
603
604 optend:
605 *databufp = NULL; /* for safety */
606 return (-1);
607 }
608
609 int
610 inet6_opt_find(void *extbuf, socklen_t extlen, int offset, u_int8_t type,
611 socklen_t *lenp, void **databufp)
612 {
613 u_int8_t *optp, *lim;
614 int optlen;
615
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;
620
621 /*
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.
625 */
626 if (offset == 0)
627 optp = (u_int8_t *)(void *)((struct ip6_hbh *)extbuf + 1);
628 else
629 optp = (u_int8_t *)extbuf + offset;
630
631 /* Find the specified option */
632 while (optp < lim) {
633 if ((optlen = ip6optlen(optp, lim)) == 0)
634 goto optend;
635
636 if (*optp == type) { /* found */
637 *lenp = optlen - 2;
638 *databufp = optp + 2;
639 return (optp + optlen - (u_int8_t *)extbuf);
640 }
641
642 optp += optlen;
643 }
644
645 optend:
646 *databufp = NULL; /* for safety */
647 return (-1);
648 }
649
650 int
651 inet6_opt_get_val(void *databuf, int offset, void *val, socklen_t vallen)
652 {
653
654 /* we can't assume alignment here */
655 memcpy(val, (u_int8_t *)databuf + offset, vallen);
656
657 return (offset + vallen);
658 }
659