1/* $NetBSD: ip6opt.c,v 1.14 2012/03/20 17:44:18 matt 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.14 2012/03/20 17:44:18 matt 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 <stddef.h> 47#include <string.h> 48#include <stdio.h> 49 50#ifdef __weak_alias 51__weak_alias(inet6_option_alloc,_inet6_option_alloc) 52__weak_alias(inet6_option_append,_inet6_option_append) 53__weak_alias(inet6_option_find,_inet6_option_find) 54__weak_alias(inet6_option_init,_inet6_option_init) 55__weak_alias(inet6_option_next,_inet6_option_next) 56__weak_alias(inet6_option_space,_inet6_option_space) 57__weak_alias(inet6_opt_init, _inet6_opt_init) 58__weak_alias(inet6_opt_append, _inet6_opt_append) 59__weak_alias(inet6_opt_finish, _inet6_opt_finish) 60__weak_alias(inet6_opt_set_val, _inet6_opt_set_val) 61__weak_alias(inet6_opt_next, _inet6_opt_next) 62__weak_alias(inet6_opt_find, _inet6_opt_find) 63__weak_alias(inet6_opt_get_val, _inet6_opt_get_val) 64#endif 65 66static int ip6optlen(uint8_t *opt, uint8_t *lim); 67static void inet6_insert_padopt(uint8_t *p, size_t len); 68 69/* 70 * This function returns the number of bytes required to hold an option 71 * when it is stored as ancillary data, including the cmsghdr structure 72 * at the beginning, and any padding at the end (to make its size a 73 * multiple of 8 bytes). The argument is the size of the structure 74 * defining the option, which must include any pad bytes at the 75 * beginning (the value y in the alignment term "xn + y"), the type 76 * byte, the length byte, and the option data. 77 */ 78int 79inet6_option_space(int nbytes) 80{ 81 size_t sp; 82 nbytes += 2; /* we need space for nxt-hdr and length fields */ 83 sp = CMSG_SPACE((nbytes + 7) & ~7); 84 _DIAGASSERT(__type_fit(int, sp)); 85 return (int)sp; 86} 87 88/* 89 * This function is called once per ancillary data object that will 90 * contain either Hop-by-Hop or Destination options. It returns 0 on 91 * success or -1 on an error. 92 */ 93int 94inet6_option_init(void *bp, struct cmsghdr **cmsgp, 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 */ 125int 126inet6_option_append(struct cmsghdr *cmsg, const uint8_t *typep, int multx, 127 int plusy) 128{ 129 size_t padlen, optlen, off; 130 register uint8_t *bp; 131 struct ip6_ext *eh; 132 133 _DIAGASSERT(cmsg != NULL); 134 _DIAGASSERT(typep != NULL); 135 136 bp = (uint8_t *)(void *)cmsg + cmsg->cmsg_len; 137 eh = (struct ip6_ext *)(void *)CMSG_DATA(cmsg); 138 139 /* argument validation */ 140 if (multx != 1 && multx != 2 && multx != 4 && multx != 8) 141 return(-1); 142 if (plusy < 0 || plusy > 7) 143 return(-1); 144 145 /* 146 * If this is the first option, allocate space for the 147 * first 2 bytes(for next header and length fields) of 148 * the option header. 149 */ 150 if (bp == (uint8_t *)(void *)eh) { 151 bp += 2; 152 cmsg->cmsg_len += 2; 153 } 154 155 /* calculate pad length before the option. */ 156 off = bp - (uint8_t *)(void *)eh; 157 padlen = (((off % multx) + (multx - 1)) & ~(multx - 1)) - 158 (off % multx); 159 padlen += plusy; 160 padlen %= multx; /* keep the pad as short as possible */ 161 /* insert padding */ 162 inet6_insert_padopt(bp, padlen); 163 _DIAGASSERT(__type_fit(socklen_t, padlen + cmsg->cmsg_len)); 164 cmsg->cmsg_len += (socklen_t)padlen; 165 bp += padlen; 166 167 /* copy the option */ 168 if (typep[0] == IP6OPT_PAD1) 169 optlen = 1; 170 else 171 optlen = typep[1] + 2; 172 memcpy(bp, typep, (size_t)optlen); 173 bp += optlen; 174 _DIAGASSERT(__type_fit(socklen_t, optlen + cmsg->cmsg_len)); 175 cmsg->cmsg_len += (socklen_t)optlen; 176 177 /* calculate pad length after the option and insert the padding */ 178 off = bp - (uint8_t *)(void *)eh; 179 padlen = ((off + 7) & ~7) - off; 180 inet6_insert_padopt(bp, padlen); 181 bp += padlen; 182 _DIAGASSERT(__type_fit(socklen_t, padlen + cmsg->cmsg_len)); 183 cmsg->cmsg_len += (socklen_t)padlen; 184 185 /* update the length field of the ip6 option header */ 186 off = bp - (uint8_t *)(void *)eh; 187 _DIAGASSERT(__type_fit(uint8_t, (off >> 3) - 1)); 188 eh->ip6e_len = (uint8_t)((off >> 3) - 1); 189 190 return(0); 191} 192 193/* 194 * This function appends a Hop-by-Hop option or a Destination option 195 * into an ancillary data object that has been initialized by 196 * inet6_option_init(). This function returns a pointer to the 8-bit 197 * option type field that starts the option on success, or NULL on an 198 * error. 199 * The difference between this function and inet6_option_append() is 200 * that the latter copies the contents of a previously built option into 201 * the ancillary data object while the current function returns a 202 * pointer to the space in the data object where the option's TLV must 203 * then be built by the caller. 204 * 205 */ 206uint8_t * 207inet6_option_alloc(struct cmsghdr *cmsg, int datalen, int multx, int plusy) 208{ 209 size_t padlen, off; 210 register uint8_t *bp; 211 uint8_t *retval; 212 struct ip6_ext *eh; 213 214 _DIAGASSERT(cmsg != NULL); 215 216 bp = (uint8_t *)(void *)cmsg + cmsg->cmsg_len; 217 eh = (struct ip6_ext *)(void *)CMSG_DATA(cmsg); 218 219 /* argument validation */ 220 if (multx != 1 && multx != 2 && multx != 4 && multx != 8) 221 return(NULL); 222 if (plusy < 0 || plusy > 7) 223 return(NULL); 224 225 /* 226 * If this is the first option, allocate space for the 227 * first 2 bytes(for next header and length fields) of 228 * the option header. 229 */ 230 if (bp == (uint8_t *)(void *)eh) { 231 bp += 2; 232 cmsg->cmsg_len += 2; 233 } 234 235 /* calculate pad length before the option. */ 236 off = bp - (uint8_t *)(void *)eh; 237 padlen = (((off % multx) + (multx - 1)) & ~(multx - 1)) - 238 (off % multx); 239 padlen += plusy; 240 padlen %= multx; /* keep the pad as short as possible */ 241 /* insert padding */ 242 inet6_insert_padopt(bp, padlen); 243 cmsg->cmsg_len += (socklen_t)padlen; 244 bp += padlen; 245 246 /* keep space to store specified length of data */ 247 retval = bp; 248 bp += datalen; 249 cmsg->cmsg_len += datalen; 250 251 /* calculate pad length after the option and insert the padding */ 252 off = bp - (uint8_t *)(void *)eh; 253 padlen = ((off + 7) & ~7) - off; 254 inet6_insert_padopt(bp, padlen); 255 bp += padlen; 256 _DIAGASSERT(__type_fit(socklen_t, padlen + cmsg->cmsg_len)); 257 cmsg->cmsg_len += (socklen_t)padlen; 258 259 /* update the length field of the ip6 option header */ 260 off = bp - (uint8_t *)(void *)eh; 261 _DIAGASSERT(__type_fit(uint8_t, (off >> 3) - 1)); 262 eh->ip6e_len = (uint8_t)((off >> 3) - 1); 263 264 return(retval); 265} 266 267/* 268 * This function processes the next Hop-by-Hop option or Destination 269 * option in an ancillary data object. If another option remains to be 270 * processed, the return value of the function is 0 and *tptrp points to 271 * the 8-bit option type field (which is followed by the 8-bit option 272 * data length, followed by the option data). If no more options remain 273 * to be processed, the return value is -1 and *tptrp is NULL. If an 274 * error occurs, the return value is -1 and *tptrp is not NULL. 275 * (RFC 2292, 6.3.5) 276 */ 277int 278inet6_option_next(const struct cmsghdr *cmsg, uint8_t **tptrp) 279{ 280 struct ip6_ext *ip6e; 281 int hdrlen, optlen; 282 uint8_t *lim; 283 284 _DIAGASSERT(cmsg != NULL); 285 _DIAGASSERT(tptrp != NULL); 286 287 if (cmsg->cmsg_level != IPPROTO_IPV6 || 288 (cmsg->cmsg_type != IPV6_HOPOPTS && 289 cmsg->cmsg_type != IPV6_DSTOPTS)) 290 return(-1); 291 292 /* message length validation */ 293 if (cmsg->cmsg_len < CMSG_SPACE(sizeof(struct ip6_ext))) 294 return(-1); 295 ip6e = __UNCONST(CCMSG_DATA(cmsg)); 296 hdrlen = (ip6e->ip6e_len + 1) << 3; 297 if (cmsg->cmsg_len < CMSG_SPACE(hdrlen)) 298 return(-1); 299 300 /* 301 * If the caller does not specify the starting point, 302 * simply return the 1st option. 303 * Otherwise, search the option list for the next option. 304 */ 305 lim = (uint8_t *)(void *)ip6e + hdrlen; 306 if (*tptrp == NULL) 307 *tptrp = (uint8_t *)(void *)(ip6e + 1); 308 else { 309 if ((optlen = ip6optlen(*tptrp, lim)) == 0) 310 return(-1); 311 312 *tptrp = *tptrp + optlen; 313 } 314 if (*tptrp >= lim) { /* there is no option */ 315 *tptrp = NULL; 316 return(-1); 317 } 318 /* 319 * Finally, checks if the next option is safely stored in the 320 * cmsg data. 321 */ 322 if (ip6optlen(*tptrp, lim) == 0) 323 return(-1); 324 else 325 return(0); 326} 327 328/* 329 * This function is similar to the inet6_option_next() function, 330 * except this function lets the caller specify the option type to be 331 * searched for, instead of always returning the next option in the 332 * ancillary data object. 333 * Note: RFC 2292 says the type of tptrp is uint8_t *, but we think 334 * it's a typo. The variable should be type of uint8_t **. 335 */ 336int 337inet6_option_find(const struct cmsghdr *cmsg, uint8_t **tptrp, int type) 338{ 339 struct ip6_ext *ip6e; 340 int hdrlen, optlen; 341 uint8_t *optp, *lim; 342 343 _DIAGASSERT(cmsg != NULL); 344 _DIAGASSERT(tptrp != NULL); 345 346 if (cmsg->cmsg_level != IPPROTO_IPV6 || 347 (cmsg->cmsg_type != IPV6_HOPOPTS && 348 cmsg->cmsg_type != IPV6_DSTOPTS)) 349 return(-1); 350 351 /* message length validation */ 352 if (cmsg->cmsg_len < CMSG_SPACE(sizeof(struct ip6_ext))) 353 return(-1); 354 ip6e = __UNCONST(CCMSG_DATA(cmsg)); 355 hdrlen = (ip6e->ip6e_len + 1) << 3; 356 if (cmsg->cmsg_len < CMSG_SPACE(hdrlen)) 357 return(-1); 358 359 /* 360 * If the caller does not specify the starting point, 361 * search from the beginning of the option list. 362 * Otherwise, search from *the next option* of the specified point. 363 */ 364 lim = (uint8_t *)(void *)ip6e + hdrlen; 365 if (*tptrp == NULL) 366 *tptrp = (uint8_t *)(void *)(ip6e + 1); 367 else { 368 if ((optlen = ip6optlen(*tptrp, lim)) == 0) 369 return(-1); 370 371 *tptrp = *tptrp + optlen; 372 } 373 for (optp = *tptrp; optp < lim; optp += optlen) { 374 if (*optp == type) { 375 *tptrp = optp; 376 return(0); 377 } 378 if ((optlen = ip6optlen(optp, lim)) == 0) 379 return(-1); 380 } 381 382 /* search failed */ 383 *tptrp = NULL; 384 return(-1); 385} 386 387/* 388 * Calculate the length of a given IPv6 option. Also checks 389 * if the option is safely stored in user's buffer according to the 390 * calculated length and the limitation of the buffer. 391 */ 392static int 393ip6optlen(uint8_t *opt, uint8_t *lim) 394{ 395 int optlen; 396 397 _DIAGASSERT(opt != NULL); 398 _DIAGASSERT(lim != NULL); 399 400 if (*opt == IP6OPT_PAD1) 401 optlen = 1; 402 else { 403 /* is there enough space to store type and len? */ 404 if (opt + 2 > lim) 405 return(0); 406 optlen = *(opt + 1) + 2; 407 } 408 if (opt + optlen <= lim) 409 return(optlen); 410 411 return(0); 412} 413 414static void 415inet6_insert_padopt(uint8_t *p, size_t len) 416{ 417 418 _DIAGASSERT(p != NULL); 419 420 switch(len) { 421 case 0: 422 return; 423 case 1: 424 p[0] = IP6OPT_PAD1; 425 return; 426 default: 427 p[0] = IP6OPT_PADN; 428 _DIAGASSERT(__type_fit(uint8_t, len - 2)); 429 p[1] = (uint8_t)(len - 2); 430 memset(&p[2], 0, len - 2); 431 return; 432 } 433} 434 435/* 436 * The following functions are defined in RFC3542, which is a successor 437 * of RFC2292. 438 */ 439 440int 441inet6_opt_init(void *extbuf, socklen_t extlen) 442{ 443 struct ip6_ext *ext = (struct ip6_ext *)extbuf; 444 445 if (ext) { 446 if (extlen == 0 || (extlen % 8)) 447 return (-1); 448 ext->ip6e_len = (extlen >> 3) - 1; 449 } 450 451 return (2); /* sizeof the next and the length fields */ 452} 453 454int 455inet6_opt_append(void *extbuf, socklen_t extlen, int offset, uint8_t type, 456 socklen_t len, uint8_t align, void **databufp) 457{ 458 int currentlen = offset; 459 size_t padlen = 0; 460 461 /* 462 * The option type must have a value from 2 to 255, inclusive. 463 * (0 and 1 are reserved for the Pad1 and PadN options, respectively.) 464 */ 465 if (type < 2) 466 return (-1); 467 468 /* 469 * The option data length must have a value between 0 and 255, 470 * inclusive, and is the length of the option data that follows. 471 */ 472 if (len > 255) 473 return (-1); 474 475 /* 476 * The align parameter must have a value of 1, 2, 4, or 8. 477 * The align value can not exceed the value of len. 478 */ 479 if (align != 1 && align != 2 && align != 4 && align != 8) 480 return (-1); 481 if (align > len) 482 return (-1); 483 484 /* Calculate the padding length. */ 485 currentlen += 2 + len; /* 2 means "type + len" */ 486 if (currentlen % align) 487 padlen = align - (currentlen % align); 488 489 /* The option must fit in the extension header buffer. */ 490 _DIAGASSERT(__type_fit(int, currentlen + padlen)); 491 currentlen += (int)padlen; 492 if (extlen && /* XXX: right? */ 493 (socklen_t)currentlen > extlen) 494 return (-1); 495 496 if (extbuf) { 497 uint8_t *optp = (uint8_t *)extbuf + offset; 498 499 if (padlen == 1) { 500 /* insert a Pad1 option */ 501 *optp = IP6OPT_PAD1; 502 optp++; 503 } else if (padlen > 0) { 504 /* insert a PadN option for alignment */ 505 *optp++ = IP6OPT_PADN; 506 _DIAGASSERT(__type_fit(uint8_t, padlen - 2)); 507 *optp++ = (uint8_t)(padlen - 2); 508 memset(optp, 0, padlen - 2); 509 optp += (padlen - 2); 510 } 511 512 *optp++ = type; 513 *optp++ = len; 514 515 *databufp = optp; 516 } 517 518 return (currentlen); 519} 520 521int 522inet6_opt_finish(void *extbuf, socklen_t extlen, int offset) 523{ 524 int updatelen = offset > 0 ? (1 + ((offset - 1) | 7)) : 0; 525 526 if (extbuf) { 527 uint8_t *padp; 528 size_t padlen = updatelen - offset; 529 530 if ((socklen_t)updatelen > extlen || padlen >= 256 + 2) 531 return (-1); 532 533 padp = (uint8_t *)extbuf + offset; 534 if (padlen == 1) 535 *padp = IP6OPT_PAD1; 536 else if (padlen > 0) { 537 *padp++ = IP6OPT_PADN; 538 *padp++ = (uint8_t)(padlen - 2); 539 memset(padp, 0, padlen - 2); 540 } 541 } 542 543 return (updatelen); 544} 545 546int 547inet6_opt_set_val(void *databuf, int offset, void *val, socklen_t vallen) 548{ 549 550 memcpy((uint8_t *)databuf + offset, val, vallen); 551 return (offset + vallen); 552} 553 554int 555inet6_opt_next(void *extbuf, socklen_t extlen, int offset, uint8_t *typep, 556 socklen_t *lenp, void **databufp) 557{ 558 uint8_t *optp, *lim; 559 int optlen; 560 561 /* Validate extlen. XXX: is the variable really necessary?? */ 562 if (extlen == 0 || (extlen % 8)) 563 return (-1); 564 lim = (uint8_t *)extbuf + extlen; 565 566 /* 567 * If this is the first time this function called for this options 568 * header, simply return the 1st option. 569 * Otherwise, search the option list for the next option. 570 */ 571 if (offset == 0) 572 optp = (uint8_t *)(void *)((struct ip6_hbh *)extbuf + 1); 573 else 574 optp = (uint8_t *)extbuf + offset; 575 576 /* Find the next option skipping any padding options. */ 577 while (optp < lim) { 578 ptrdiff_t rv; 579 switch(*optp) { 580 case IP6OPT_PAD1: 581 optp++; 582 break; 583 case IP6OPT_PADN: 584 if ((optlen = ip6optlen(optp, lim)) == 0) 585 goto optend; 586 optp += optlen; 587 break; 588 default: /* found */ 589 if ((optlen = ip6optlen(optp, lim)) == 0) 590 goto optend; 591 *typep = *optp; 592 *lenp = optlen - 2; 593 *databufp = optp + 2; 594 rv = optp + optlen - (uint8_t *)extbuf; 595 _DIAGASSERT(__type_fit(int, rv)); 596 return (int)rv; 597 } 598 } 599 600 optend: 601 *databufp = NULL; /* for safety */ 602 return (-1); 603} 604 605int 606inet6_opt_find(void *extbuf, socklen_t extlen, int offset, uint8_t type, 607 socklen_t *lenp, void **databufp) 608{ 609 uint8_t *optp, *lim; 610 int optlen; 611 612 /* Validate extlen. XXX: is the variable really necessary?? */ 613 if (extlen == 0 || (extlen % 8)) 614 return (-1); 615 lim = (uint8_t *)extbuf + extlen; 616 617 /* 618 * If this is the first time this function called for this options 619 * header, simply return the 1st option. 620 * Otherwise, search the option list for the next option. 621 */ 622 if (offset == 0) 623 optp = (uint8_t *)(void *)((struct ip6_hbh *)extbuf + 1); 624 else 625 optp = (uint8_t *)extbuf + offset; 626 627 /* Find the specified option */ 628 while (optp < lim) { 629 if ((optlen = ip6optlen(optp, lim)) == 0) 630 goto optend; 631 632 if (*optp == type) { /* found */ 633 ptrdiff_t td; 634 *lenp = optlen - 2; 635 *databufp = optp + 2; 636 td = optp + optlen - (uint8_t *)extbuf; 637 _DIAGASSERT(__type_fit(int, td)); 638 return (int)td; 639 } 640 641 optp += optlen; 642 } 643 644 optend: 645 *databufp = NULL; /* for safety */ 646 return (-1); 647} 648 649int 650inet6_opt_get_val(void *databuf, int offset, void *val, socklen_t vallen) 651{ 652 653 /* we can't assume alignment here */ 654 memcpy(val, (uint8_t *)databuf + offset, vallen); 655 656 return (offset + vallen); 657} 658