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